ICPC NEAU Programming Contest 2020 [ 隨便置換]
ICPC NEAU Programming Contest 2020 隨便置換
題目大意:
中文題就自己看吧
題解:
這個題目還是很複雜的,挺繞的,想說明白也需要很強的表述能力。
-
首先假設 \(a\) 表示原始串,\(c\) 表示目標串,\(p\) 表示置換串,那麼題目中已知的等式關係是 \(a=b*p^m\) 求 \(p\)
-
第一步先求 \(p^m\) 這個應該不太難求
-
直白的來說就是直接把 \(p^m\) 當作一個置換串 \(s\) ,已知原串和目標串,求這個中間置換串 \(s\)
-
完成第一步之後,這個目標串和原始串就沒什麼太大的意義了,接下來看這個中間置換串 \(s\)
-
\(s\) 也就是 \(p^m\), 這個是一個變化串,他把 \(a\) 按照本身的變化規律變成了 \(c\) 串。
-
接下來說說 \(s\) 串的變化規律:
-
\(s[1]=6\) 表示原來第1個數變成了6
-
\(s[2]=4\) 表示原來第2個數變成了4,\(s[i]\) 表示的原來的第 \(i\) 個變成了第 \(s[i]\)
-
... 以此類推
-
-
已知 \(s\) 串的變化規律,那麼我要求 \(s^2\) 這個串,那是不是相當於原來的 \(s\) 串往後挪了兩位,\(s[1]\) 變成了6之後又變成了3,\(s[2]\) 變成了4之後又變成了7,所以可以得到 \(s^2=3745621\)
這個圖就是上面哪個圖兩格兩格的跳變化出來的圖,就是說 \(s\) 串按照 \(s\) 串的規律變化一次之後任選一個起點,之後的第 \(i\) 點是原來的這個點的位置往後跳 \(i*2\)
-
接下來討論\(p\) 的變化規律,已知 \(p^m=p*p...*p\) ,所以 \(p^m\) 是原來 \(p\) 往後跳 \(m\) 格形成的圖。
-
此例題的 \(m=2\) ,先任意選一個起點,假設起點是1:
-
那麼1後面的第 \(m\) 個格子是6,b[2]=6
b[0] = 1 b[1]=0 b[2]=6 b[3]=0 b[4]=0 b[5]=0 b[6]=0
-
那麼6後面的第 2個格子是3,所以第4個 b[4]=3
b[0] = 1 b[1]=0 b[2]=6 b[3]=0 b[4]=3 b[5]=0 b[6]=0
-
以此類推,最後可以得出這個 \(p\) 的變化規律
b[0] = 1 b[1]=4 b[2]=6 b[3]=7 b[4]=3 b[5]=5 b[6]=2
-
-
這個變化規律圖得到之後,就可以求 \(p\) 這串是什麼了,這個的求法和之前從串變成規律圖操作差不多。
\(p[1]=4\) \(p[4]=6\) ...
以上都是這個題目的整體求解思路,程式碼中有部分細節講解。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
int a[maxn],f[maxn],cnt[maxn],b[maxn],p[maxn];
int Find(int x){
return x==f[x]?x:f[x]=Find(f[x]);
}
void init(int n){
for(int i=1;i<=n;i++) f[i]=i,cnt[i]=0;
}
int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
int main(){
int t;
scanf("%d",&t);
while(t--){
int n,m;
scanf("%d%d",&n,&m);
init(n);
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
a[x]=i;
if(Find(i)!=Find(x)){
f[i]=x;
/*
* 這一步再為求環上的節點數量做準備
* 也可以用dfs求環上的節點數量
* 但是這些都是簡單環,所以用並查集比較簡單
*/
}
}
for(int i=1;i<=n;i++) cnt[Find(i)]++;
/*
* a就是p^m這個串
* cnt[i]表示以i點為根節點的這個環的節點數量
* 接下來求判斷是否有解,因為題目可能不止一個環,所以要判斷每一個環的大小是不是都是與m互質
* 如果不互質,那麼不可能找到答案,因為不互質,那麼總會有一些點找不到
*/
int flag = 1;
for(int i=1;i<=n;i++){
if(!cnt[i]) continue;
if(gcd(cnt[i],m)!=1){
flag = 0;
break;
}
}
if(!flag){
printf("NO\n");
continue;
}
/*
* 判斷有解之後就開始求p這個串的變化規律了
*/
// for(int i=1;i<=n;i++) printf("a[%d]=%d\n",i,a[i]);
for(int i=1;i<=n;i++){
if(!cnt[i]) continue;
int l = cnt[i], w = i;
for(int j=0;j<l;j++){
b[j*1ll*m%l] = w;
w=a[w];
// printf("b[%d]=%d\n",j*m%l,b[j*1ll*m%l]);
}
for(int j=0;j<l;j++){
p[b[j]]=b[(j+1)%l];
}
/*
* 這裡是在求p串,w首先等於i,表示將i定為起點
* 所以b[0]=w
* 從這個點往後跳m格b[m]=a[w]
* 得到b串之後再轉化為p串即可
* !!!注意這個位置p[b[j]]=b[(j+1)%l]
* 這是因為我存的這個變化串是 a[i] 表示的是原來的第i個變成了第a[i]個
* 。。。。好像沒有講清楚,自己思考吧,差不多就是怎麼變成圖的就怎麼變回來
* 假設 原來是 x1->x2 a[x2]=x1
* 那麼之後的 x1->x2 p[x1]=x2
*/
}
printf("YES\n");
for(int i=1;i<n;i++) printf("%d ",p[i]);
printf("%d\n",p[n]);
}
}