bzoj 1965 //1965:[Ahoi2005] SHUFFLE 洗牌 //在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=1965
更多题解,详见https://blog.csdn.net/mrcrack/article/details/90228694BZOJ刷题记录
方法一:乘法逆元+快速幂+快速乘
33ms / 776.00KB / 476B C++
//1965:[Ahoi2005] SHUFFLE 洗牌
//在线测评地址https://www.luogu.org/problem/P2054
//此文https://www.cnblogs.com/chenxiaoran666/p/BZOJ1965.html思路写得不错,摘抄如下:
作个解释:2019-10-22 20:37
移动第1次后的位置为2x%(n+1)
移动第2次后的位置为2*(2x%(n+1))%(n+1)=x*2^2%(n+1)
移动第3次后的位置为2*(x*2^2%(n+1))%(n+1)=x*2^3%(n+1)
......
移动第m次后的位置为x*2^m%(n+1)
//此文https://blog.csdn.net/Fsss_7/article/details/50648297思路不错,摘抄如下:
/*
一个置换的题。。我们对样例进行简单的分析可知经过一次变换有(1->2,2->4,3->6,4->1,5->3,6->5),然后我们就能发现p[a]=a*2%(n+1)。。然后我们设答案为X即X=p[l],所以有X*(2^m)=l%(n+1)并且2模n+1的逆元为n/2+1,所以我们将等式变换一下就有X=l*(n/2+1)^m%(n+1),然后就只要用快速幂处理这个式子就行了,同时要注意数据是10^10可能乘爆,用快速乘即可。
解释一下:n+1为奇数,2*(n/2+1)=1%(n+1)故2^m*(n/2+1)^m=1%(n+1),(n/2+1)^m为2^m关于模(n+1)的乘法逆元。
*/
//因0<N≤10^10 ,0 ≤M≤10^10,乘法long long要溢出,故采用快速乘。
//样例通过,提交AC.2019-10-22 21:33
#include
#define LL long long
LL n,m,L;
LL quick_mul(LL a,LL b){//5*7 b=7 ans=5 a=10;b=3 ans=15 a=20;b=1 ans=35 a=40;b=0
LL ans=0;
while(b){
if(b&1)ans=(ans+a)%(n+1);
a=(a+a)%(n+1);
b>>=1;
}
return ans;
}
LL quick_pow(LL a,LL b){
LL ans=1;
while(b){
if(b&1)ans=quick_mul(ans,a);
a=quick_mul(a,a);
b>>=1;
}
return ans;
}
int main(){
scanf("%lld%lld%lld",&n,&m,&L);
printf("%lld\n",quick_mul(L,quick_pow(n/2+1,m)));
return 0;
}
方法二:拓展欧几里得算法+快速幂+快速乘
bzoj 1965
820 kb | 60 ms | C++/Edit | 918 B |
洛谷36ms / 788.00KB / 693B C++
//此文https://www.luogu.org/problemnew/solution/P2054 作者: Smokey_Days 更新时间: 2018-06-29 21:53思路写得不错,摘抄如下
//样例通过,提交14分,测试点1,3-9,11-13WA.2019-10-23
//排查,发现
/*
x=x*L%p;//此处错写成x*=L;
*/
//提交84分,测试点13WA.
//继续排查,发现
/*
x=qmul(x,L);//此处错写成x=x*L%p;
*/
//提交AC.2019-10-23
#include
#define LL long long
LL n,m,L,p;
LL qmul(LL a,LL b){
LL ans=0;//此处错写成LL ans=1;
while(b){
if(b&1)ans=(ans+a)%p;
a=(a+a)%p;
b>>=1;
}
return ans;
}
LL qpow(LL a,LL b){
LL ans=1;
while(b){
if(b&1)ans=qmul(ans,a);
a=qmul(a,a);
b>>=1;
}
return ans;
}
LL exgcd(LL a,LL b,LL *x,LL *y){
LL d,t;
if(b==0){
d=a,*x=1,*y=0;
return d;
}
d=exgcd(b,a%b,x,y);//此处错写成d=exgcd(b,a%b,*x,*y);
t=*x,*x=*y,*y=t-a/b**y;//qmul(a/b,*y)
return d;
}
int main(){
LL x,y;
scanf("%lld%lld%lld",&n,&m,&L),p=n+1;
exgcd(qpow(2,m),p,&x,&y);
x=(x%p+p)%p;
x=qmul(x,L);//此处错写成x=x*L%p;//此处错写成x*=L;
printf("%lld\n",x);
return 0;
}
方法三:打表找规律
//此文https://www.luogu.org/problemnew/solution/P2054 作者: star_city 更新时间: 2017-08-29 17:01思路写得不错,摘抄如下
/*
我们发现,每次洗牌时,若l为偶数,l /= 2; 若l为奇数,则l = l/2 + n/2 + 1
暴力的循环m次可以得70分
改进:
若发现l经过x次洗牌后又被洗回了l的位置
比如n = 2, m = 1000000001, l = 1
此时x = 2(每洗牌2次就回到原点),那么洗1000000001次就相当于洗了一次
因此我们只要当l又回到原点时用m %= x即可
时间复杂度无(bu)法(hui)表示
*/
//以下代码执行结果如上
#include
int n,m,L;
int main(){
int x,i;
scanf("%d%d%d",&n,&m,&L),x=L;
for(i=1;i<=m;i++){
if(x%2==0)x/=2;
else x=x/2+n/2+1;
}
printf("%d\n",x);
return 0;
}
//以下代码执行结果如上
#include
int n,m,L;
int main(){
int x,i;
scanf("%d%d%d",&n,&m,&L),x=L;
for(i=1;i<=m;i++){
if(x%2==0)x/=2;
else x=x/2+n/2+1;
if(x==L)break;
}
m%=i,x=L;//i为循环节长度
for(i=1;i<=m;i++){
if(x%2==0)x/=2;
else x=x/2+n/2+1;
}
printf("%d\n",x);
return 0;
}