【ACWing】3579. 数字移动

题目地址:

https://www.acwing.com/problem/content/3582/

1 ∼ n 1∼n 1n按顺序排成一排,构成一个数列。数字 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 1n的一种排列。接下来,我们会重复不断地对数列进行如下操作:重新排列数列中每个数的位置,将位于位置 i i i的数移动至位置 p i p_i pi。(如果 i = p i i=p_i i=pi则该数仍移动至位置 i i i)。每次操作开始时,所有数的移动同时进行,操作结束后,数列将变为一个新的 1 ∼ n 1∼n 1n的排列。例如,当 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 1T10 1 ≤ n ≤ 10 1≤n≤10 1n10
对于 100 100% 100的数据, 1 ≤ T ≤ 1000 1≤T≤1000 1T1000 1 ≤ n ≤ 2 × 1 0 5 1≤n≤2×10^5 1n2×105 1 ≤ p i ≤ n 1≤p_i≤n 1pin
保证 p 1 ∼ p n p_1∼p_n p1pn 1 ∼ n 1∼n 1n的一种排列。
保证 ∑ n ≤ 2 × 1 0 5 ∑n≤2×10^5 n2×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(nlogn),空间 O ( n ) O(n) O(n)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值