bzoj 1965 //1965:[Ahoi2005] SHUFFLE 洗牌

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思路写得不错,摘抄如下:

bzoj 1965 //1965:[Ahoi2005] SHUFFLE 洗牌_第1张图片

作个解释: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思路写得不错,摘抄如下

bzoj 1965 //1965:[Ahoi2005] SHUFFLE 洗牌_第2张图片

bzoj 1965 //1965:[Ahoi2005] SHUFFLE 洗牌_第3张图片

bzoj 1965 //1965:[Ahoi2005] SHUFFLE 洗牌_第4张图片

bzoj 1965 //1965:[Ahoi2005] SHUFFLE 洗牌_第5张图片

//样例通过,提交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)表示
*/

bzoj 1965 //1965:[Ahoi2005] SHUFFLE 洗牌_第6张图片

//以下代码执行结果如上

#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;
}

bzoj 1965 //1965:[Ahoi2005] SHUFFLE 洗牌_第7张图片

//以下代码执行结果如上

#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;
}

你可能感兴趣的:(跟着大佬学算法)