1. 程式人生 > 實用技巧 >BZOJ-1965 [Ahoi2005]SHUFFLE 洗牌(exgcd)

BZOJ-1965 [Ahoi2005]SHUFFLE 洗牌(exgcd)

題目描述

  對於撲克牌的一次洗牌是這樣定義的,將一疊 \(n\)\(n\) 為偶數)張撲克牌平均分成上下兩疊,取下面一疊的第一張作為新的一疊的第一張,然後取上面一疊的第一張作為新的一疊的第二張,再取下面一疊的第二張作為新的一疊的第三張 \(\cdots\) 如此交替直到所有的牌取完。

  如果對一疊 \(6\) 張的撲克牌 \([1,2,3,4,5,6]\),進行一次洗牌的過程如下圖所示:

  從圖中可以看出經過一次洗牌,序列 \([1,2,3,4,5,6]\) 變為 \([4,1,5,2,6,3]\)。當然,再對得到的序列進行一次洗牌,又會變為 \([2,4,6,1,3,5]\)

  遊戲是這樣的,如果給定長度為 \(n\)

的一疊撲克牌,並且牌面數字從 \(1\) 開始連續增加到 \(n\)(不考慮花色),對這樣的一疊撲克牌,進行 \(m\) 次洗牌,求經過洗牌後的撲克牌序列中第 \(k\) 張撲克牌的牌面數字大小。

  資料範圍:\(0<n\leq 10^{10},0\leq m\leq 10^{10},n\) 為偶數。

分析

  假設初始的序列為 \([1,2,3,4,5,6,7,8,9,10]\),一次洗牌後變成 \([6,1,7,2,8,3,9,4,10,5]\)

  可以發現 \(1,2,3,4,5\) 的位置分別變成了 \(2,4,6,8,10\),即變成了原來的二倍;\(6,7,8,9,10\) 的位置分別變成了 \(1,3,5,7,9\)

。即撲克 \(x\) 的位置變成了 \(2x \mod {(n+1)}\)

  繼續洗牌,序列 \([6,1,7,2,8,3,9,4,10,5]\) 變成了 \([3,6,9,1,4,7,10,2,5,8]\),每張撲克 \(x\) 的位置變成了 \(4x\mod {(n+1)}\)(和原序列相比)。

  歸納一下,經過 \(m\) 輪洗牌後,撲克 \(x\) 的位置變成了 \(2^m x\mod {(n+1)}\)

  欲求第 \(k\) 張牌的原始位置,可以列出同餘方程 \(2^{m}x\equiv k\pmod {n+1}\)

  轉化為 \(2^mx+(n+1)y=k\Longrightarrow 2^m\frac{x}{k}+(n+1)\frac{y}{k}=1\)

  擴充套件歐幾里得演算法求出 \(\frac{x}{k}\),再乘 \(k\) 即可得到原位置 \(x\)

程式碼

#include<bits/stdc++.h>
using namespace std;
long long n,m,k;
long long quick_mul(long long a,long long b,long long mod)
{
    long long ans=0;
    while(b)
    {
        if(b&1)
            ans=(ans+a)%mod;
        a=(a+a)%mod;
        b>>=1;
    }
    return ans%mod;
}
long long quick_pow(long a,long long b,long long mod)
{
    long long ans=1;
    while(b)
    {
        if(b&1)
            ans=quick_mul(ans,a,mod);
        a=quick_mul(a,a,mod);
        b>>=1;
    }
    return ans%mod;
}
long long exgcd(long long a,long long b,long long &x,long long &y)
{
    if(!b)
    {
        x=1;
        y=0;
        return a;
    }
    long long gcd=exgcd(b,a%b,y,x);
    y=y-x*(a/b);
    return gcd;
}
int main()
{
    cin>>n>>m>>k;
    long long x,y;
    exgcd(quick_pow(2,m,n+1),n+1,x,y);
    x=(x%(n+1)+(n+1))%(n+1);
    cout<<quick_mul(x,k,(n+1))<<endl;
    return 0;
}