题目地址:
https://www.acwing.com/problem/content/3582/
将 1 ∼ n 1∼n 1∼n按顺序排成一排,构成一个数列。数字 i i i刚好位于位置 i i i。再给定一个长度为 n n n的位置序列 p 1 , p 2 , … , p n p_1,p_2,…,p_n p1,p2,…,pn,它是 1 ∼ n 1∼n 1∼n的一种排列。接下来,我们会重复不断地对数列进行如下操作:重新排列数列中每个数的位置,将位于位置 i i i的数移动至位置 p i p_i pi。(如果 i = p i i=p_i i=pi则该数仍移动至位置 i i i)。每次操作开始时,所有数的移动同时进行,操作结束后,数列将变为一个新的 1 ∼ n 1∼n 1∼n的排列。例如,当 n = 6 n=6 n=6并且 p = [ 4 , 6 , 1 , 3 , 5 , 2 ] p=[4,6,1,3,5,2] p=[4,6,1,3,5,2]时,第一次操作后,数字 1 1 1将移动至位置 4 4 4,数字 2 2 2将移动至位置 6 6 6,以此类推;第二次操作后,数字 1 1 1将移动至位置 3 3 3,数字 2 2 2将移动至位置 2 2 2,以此类推。你的任务是确定从 1 1 1到 n n n的每个数字 i i i,经过多少次操作后,第一次重新回到位置 i。
例如,考虑 p=[5,1,2,4,3],数字 1 的移动轨迹如下:
第一次操作后,到达位置 5。
第二次操作后,到达位置 3。
第三次操作后,到达位置 2。
第四次操作后,回到位置 1。
所以,经过四次操作后,数字
1
1
1第一次回到位置
1
1
1。值得一提的是,数字
4
4
4经过一次操作后就回到了位置
4
4
4。
输入格式:
第一行包含整数
T
T
T,表示共有
T
T
T组测试数据。每组数据第一行包含整数
n
n
n。第二行包含
n
n
n个整数
p
1
,
…
,
p
n
p_1,…,p_n
p1,…,pn。
输出格式:
每组数据输出一行结果,包含
n
n
n个整数,其中第
i
i
i个整数表示数字
i
i
i第一次回到位置
i
i
i所经过的操作次数。整数之间用单个空格隔开。
数据范围:
对于
30
30%
30的数据,
1
≤
T
≤
10
1≤T≤10
1≤T≤10,
1
≤
n
≤
10
1≤n≤10
1≤n≤10。
对于
100
100%
100的数据,
1
≤
T
≤
1000
1≤T≤1000
1≤T≤1000,
1
≤
n
≤
2
×
1
0
5
1≤n≤2×10^5
1≤n≤2×105,
1
≤
p
i
≤
n
1≤p_i≤n
1≤pi≤n。
保证
p
1
∼
p
n
p_1∼p_n
p1∼pn是
1
∼
n
1∼n
1∼n的一种排列。
保证
∑
n
≤
2
×
1
0
5
∑n≤2×10^5
∑n≤2×105(一个输入中的
T
T
T个
n
n
n相加之和不超过
2
×
1
0
5
2×10^5
2×105)。
根据抽象代数里的相关定理,每个置换都可以分解为若干不相交的轮换的乘积,那么每个数变换多少次回到自己相当于问其所在的轮换的元素个数,可以用并查集来做。代码如下:
#include <iostream>
using namespace std;
const int N = 2e5 + 10;
int n;
int p[N], sz[N];
int find(int x) {
if (x != p[x]) p[x] = find(p[x]);
return p[x];
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
for (int i = 1; i <= n; i++) {
p[i] = i;
sz[i] = 1;
}
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
int pi = find(i), px = find(x);
if (pi != px) {
p[pi] = px;
sz[px] += sz[pi];
}
}
for (int i = 1; i <= n; i++) printf("%d ", sz[find(i)]);
puts("");
}
}
每组数据时间复杂度 O ( n log ∗ n ) O(n\log^*n) O(nlog∗n),空间 O ( n ) O(n) O(n)。