📜  门| GATE-CS-2005 |第 43 题(1)

📅  最后修改于: 2023-12-03 15:12:41.056000             🧑  作者: Mango

门| GATE-CS-2005 | 第 43 题

该题涉及到数学中的置换(group theory)。

题目描述

给定一个 $n$ 阶置换(permutation),即一个长度为 $n$ 的数组 $A$,数组中的每个元素 $A_i$ 代表将 $i$ 映射到 $A_i$,例如 $A = [4, 1, 3, 2]$ 表示 $1$ 映射到 $4$,$2$ 映射到 $1$,$3$ 映射到 $3$,$4$ 映射到 $2$。现在定义一个 $k$ 置换($k \geq 2$)是对置换进行 $k$ 次映射操作,即 $k-1$ 次对置换本身的映射,最后一次对得到的置换进行映射。

例如给定一个 $n=3$ 的置换 $[3,2,1]$,则它的 $2$ 置换为 $[1,3,2]$,即首先将原置换映射到 $[2,1,3]$,然后再将该置换映射到 $[1,3,2]$。

现在给定一个 $n$ 阶置换 $A$ 和一个正整数 $k$,要求计算 $k$ 置换之后的置换。

解题思路

考虑将置换看作一张图,将节点 $i$ 连向节点 $A_i$,这样就得到了一张 $n$ 个节点的有向图。在这张有向图上进行 $k$ 次操作,相当于将原节点走 $k$ 步后到达的节点作为新的映射数组。因为节点之间没有边可以走回去,所以 $k$ 会在一定步数内达到一定的循环节。因此我们只需要计算 $k$ 的循环节,然后将 $k$ 对循环节长度取模,最后进行一次循环即可。

具体来说,我们可以使用快慢指针算法,类似 Floyd 算法中的求环,不过需要注意一下映射数组中的下标从 $1$ 开始,因此需要将数组下标转化为从 $0$ 开始。参考代码如下:

def permutation_after_k_times(A: List[int], k: int) -> List[int]:
    n = len(A)
    A = [x - 1 for x in A]  # 将下标从1开始转为从0开始

    slow, fast = 0, 0
    for i in range(k):
        while True:
            slow = A[slow]
            fast = A[A[fast]]
            if slow == fast:
                break

        loop = []
        ptr = slow
        while True:
            loop.append(ptr)
            ptr = A[ptr]
            if ptr == slow:
                break

        for j in range(n):
            A[j] = loop[(loop.index(A[j]) + k - i) % len(loop)]

    return [x + 1 for x in A]  # 将下标转回来

这里的 slowfast 分别表示快慢指针,每次将快指针向前移动两步,慢指针向前移动一步,当它们相遇时说明存在环。然后我们将找到的环放入 loop 数组中,并计算出在第 $k-i$ 次操作后每个数应当映射到哪个位置,并更新 A 数组即可。

总结

本题利用了数学中置换的性质,对循环节进行了分析。算法的时间复杂度为 $O(n^2)$。