3579. 数字移动

将 1∼n1∼n 按顺序排成一排,构成一个数列。

数字 ii 刚好位于位置 ii。

再给定一个长度为 nn 的位置序列 p1,p2,…,pnp1,p2,…,pn,它是 1∼n1∼n 的一种排列。

接下来,我们会重复不断地对数列进行如下操作:

  • 重新排列数列中每个数的位置,将位于位置 ii 的数移动至位置 pipi。(如果 i=pii=pi 则该数仍移动至位置 ii)。
  • 每次操作开始时,所有数的移动同时进行,操作结束后,数列将变为一个新的 1∼n1∼n 的排列。

例如,当 n=6n=6 并且 p=[4,6,1,3,5,2]p=[4,6,1,3,5,2] 时,第一次操作后,数字 11 将移动至位置 44,数字 22 将移动至位置 66,以此类推;第二次操作后,数字 11 将移动至位置 33,数字 22 将移动至位置 22,以此类推。

你的任务是确定从 11 到 nn 的每个数字 ii,经过多少次操作后,第一次重新回到位置 ii。

例如,考虑 p=[5,1,2,4,3]p=[5,1,2,4,3],数字 11 的移动轨迹如下:

  • 第一次操作后,到达位置 55。
  • 第二次操作后,到达位置 33。
  • 第三次操作后,到达位置 22。
  • 第四次操作后,回到位置 11。

所以,经过四次操作后,数字 11 第一次回到位置 11。

值得一提的是,数字 44 经过一次操作后就回到了位置 44.

输入格式

第一行包含整数 TT,表示共有 TT 组测试数据。

每组数据第一行包含整数 nn。

第二行包含 nn 个整数 p1,…,pnp1,…,pn。

输出格式

每组数据输出一行结果,包含 nn 个整数,其中第 ii 个整数表示数字 ii 第一次回到位置 ii 所经过的操作次数。

整数之间用单个空格隔开。

数据范围

对于 30%30% 的数据,1≤T≤101≤T≤10,1≤n≤101≤n≤10。
对于 100%100% 的数据,1≤T≤10001≤T≤1000,1≤n≤2×1051≤n≤2×105,1≤pi≤n1≤pi≤n。
保证 p1∼pnp1∼pn 是 1∼n1∼n 的一种排列。
保证 ∑n≤2×105∑n≤2×105(一个输入中的 TT 个 nn 相加之和不超过 2×1052×105)。

输入样例:

6
5
1 2 3 4 5
3
2 3 1
6
4 6 2 1 5 3
1
1
4
3 4 1 2
5
5 1 2 4 3

输出样例

1 1 1 1 1 
3 3 3 
2 3 3 2 1 3 
1 
2 2 2 2 
4 4 4 1 4 

思路:题目中描述的一个数经过若干操作仍然能回到这个数,那么必定是某几个数组成一个环,我们只需要并查集把每对应数字合并成若干环,每个数的操作次数就是他所在环的长度

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+5;
int n;
int f[N],s[N];
int find(int x) { // 并查集
	if (f[x] != x) f[x] = find(f[x]);
	return f[x];
}
int main() {
	int T;
	cin>>T;
	while(T--) {
		scanf("%d",&n);
		for(int i=1; i<=n; i++) {
			f[i]=i;
			s[i]=1;
		}
		for(int i=1; i<=n; i++) {
			int x;
			scanf("%d",&x);
			if(find(x)!=find(i)) {
				s[find(i)]+=s[find(x)];
				f[find(x)]=find(i);
			}
		}
		for(int i=1; i<=n; i++) {
			cout<<s[find(i)]<<' ';
		}
		cout<<endl;

	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值