P9185 [USACO23OPEN] Rotate and Shift B 题解
首先,我们很容易就能得出一个显而易见的结论:
若令原数组为
的操作就等价于将
再进行上述操作,最后逆时针旋转
因为顺时针和逆时针的旋转方向是相反的,都旋转
根据上述结论,题目所说的操作流程可以如下:
(其中
这个过程中的顺时针和逆时针可以抵消,于是化简后的结果如下:
这样化简之后,每分钟的操作都变成了
于是整个题目的操作流程变成了每分钟进行一次
但是每次
那么如何加快
具体而言:
-
建立倍增数组数组
jmp ,其中jmp_{i,j} 表示用2^j 次S 操作能够把i 挪到的位置。 -
首先预处理
jmp 数组,令jmp_{b_i,0}=i ,并令jmp_{i,j}=jmp_{jmp_{i,j-1},j-1} (即从i 用2^{j-1} 次S 操作挪到的位置开始再进行2^{j-1} 次S 操作。 -
接着枚举
2 的k 次幂,若2^k \le T 则进行2^k 次S 操作。
最后别忘了逆时针再旋转
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,t,tt; //tt是t的备份
int jmp[200031][64]; //倍增数组
int sor1[200031],sor2[200031],rot[200031],tmp[200031]; //order 数组,order 数组备份、A 数组、旋转辅助数组
void rotate1(){ //R 操作
memset(tmp,-1,sizeof(tmp));
for(int i=1;i<k;i++) tmp[rot[i]]=sor2[rot[i-1]];
tmp[rot[0]]=sor2[rot[k-1]];
for(int i=0;i<n;i++)
if(tmp[i]+1) sor2[i]=tmp[i];
}
void rotate2(){ //顺时针旋转
memset(tmp,0,sizeof(tmp));
for(int i=0;i<n-1;i++) tmp[i]=sor2[i+1];
tmp[n-1]=sor2[0];
memcpy(sor2,tmp,sizeof(tmp));
}
void rotate3(){ //逆时针旋转
memset(tmp,0,sizeof(tmp));
for(int i=0;i<n;i++) tmp[i]=sor1[(i-tt%n+n)%n];
memcpy(sor1,tmp,sizeof(tmp));
}
signed main(){
cin>>n>>k>>t;
for(int i=0;i<k;i++) cin>>rot[i];
for(int i=0;i<n;i++) sor1[i]=sor2[i]=i;
tt=t;
rotate1(); //进行一次 R 操作 + 逆时针旋转一次,避免特判
rotate2();
for(int i=0;i<n;i++) jmp[sor2[i]][0]=i; //预处理 jmp 数组
for(int j=1;j<=40;j++) //递推 jmp 数组
for(int i=0;i<n;i++)
jmp[i][j]=jmp[jmp[i][j-1]][j-1];
for(int i=40;i>=0;i--){ //枚举 2^i
if((1ll<<i)<=t){
t-=(1ll<<i);
for(int j=0;j<n;j++) tmp[jmp[j][i]]=sor1[j]; //进行 2^i 次 S 操作
memcpy(sor1,tmp,sizeof(tmp));
}
}
rotate3(); //逆时针旋转 T 次
for(int i=0;i<n;i++) cout<<sor1[i]<<' ';
return 0;
}