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\)
資料範圍:\(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\)
繼續洗牌,序列 \([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;
}