2020牛客多校第二場
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(""); }