2020牛客多校第二場 J題Just Shuffle(置換群與逆元)
題意:給一個排列(1,2,3,4,5...n),將它進行置換,x次置換后變成數組a,置換的意思是將第i位的元素替換到第j位,被替換的位置j必須與其他位替換,想當與要你定一個交換關系,之后每次置換都要按着交換關系來,x次時變成a數組。如果還有困惑可以學習一下我看到的一個大佬解釋的置換群的基本定理:傳送門
這個題想了一段時間的都估計能想到去找環,例如:5 4 1 2 3,你應該會發現,5和1換了,1和3換了,3和5換了,2和4換了,4和2換了,畫出圖應該是:

然后就是說,成環的肯定是要放一起考慮,因為很明顯數字1必然可以經過1,3,5這3個位置,而且但它置換次數為其環大小時會變回原來的序列,我們不妨設某個環的大小為len,那么k必然是環原本的序列進行k%len次置換后的樣子,然后就是你已知這個環k%len次置換的結果要你求一次置換的結果,枚舉所有環,這是我比賽時的思路,但沒學過置換群,我都不知道我用這些條件找不到置換一次的結果是什么,想假思路想了一下午,如圖。

為什么找不到?因為我不知道轉換關系,如果要枚舉轉換關系,那枚舉量特別大到還要判斷k次置換后是否等於a數組。看了一晚上題解弄懂后,發現還是自己太笨了。講之前再強調一下,環再置換len次以后會變回原來的樣子,轉換k次等同於轉化k%len次,我們讓t=k%len,並且轉換一次的答案等同於轉換(?*len+1)次。現在我不一次一次的置換了,我一次就置換t次,那么a數組想當與我一次操作即置換t次后的結果,那么我進行x次操作,如果剛好,使其(x)乘(t)剛好等於某個(?)乘(len)+1不就是答案了嗎(由於k的數據原因好像是不會出現找不到的情況,即不存在輸出-1的可能)。列出公式即x * k % len=1,求出x既可,x * k % len = 1這個公式有沒有很熟悉傳送門。思路如圖:

現在我們求出了逆元x(擴展gcd或者直接for暴力x(x<len)判斷是否復合公式),要怎么構造答案,或者說怎么移動,如用b數組表示答案,c數組表示轉換關系,b[c[i]]=c[(i+x)%len],把環看成有12個有數字的時針一個鍾,如果動一次就是每個時針走一下,走x下就每個時針走x%len下,走12下回到原點,這個公式是置換群的整數冪運算公式,這邊引用一個大佬的博客傳送門
以下代碼部分:
#include<iostream>
#include<vector>
#define ll long long
using namespace std;
int n,k,to[100007],vis[100007],ans[100007];
int a[100007];
vector<int>ho;
void dfs(int p){
vis[p]=1;
ho.push_back(a[p]);
if(vis[to[p]]==0){
dfs(to[p]);
}
}
void go(){
int len=ho.size(),inv;
for(int i=0;i<len;i++){
if((ll)i*k%len==1){
inv=i;
break;
}
}
for(int i=0;i<len;i++){
ans[ho[i]]=ho[(i+inv)%len];
}
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
to[i]=a[i];
}
for(int i=1;i<=n;i++){
ho.clear();
if(vis[i]==0){
dfs(i);
go();
}
}
for(int i=1;i<=n;i++){
printf("%d ",ans[i]);
}puts("");
}
