BZOJ4589 Hard Nim(快速沃爾什變換FWT)
這是我第一道獨立做出來的FWT的題目,所以寫篇隨筆紀念一下。
(這還要紀念,我太弱了)
題目鏈接:
BZOJ
題目大意:兩人玩nim遊戲(多堆石子,每次可以從其中一堆取任意多個,不能操作就輸)。$T$ 組數據,現在問如果 $n$ 堆石子,每堆石子個數都是不超過 $m$ 的素數,有多少種不同的石子序列使得先手沒有必勝策略,答案對 $10^9+7$ 取模。(石子堆順序不同也算不同)
$1\leq T\leq 80,1\leq n\leq 10^9,1\leq m\leq 5\times 10^4$。
首先肯定要把 $m$ 以內的素數篩出來。
nim遊戲的SG函數大家都知道吧!就是它本身。
所以若先手沒有必勝策略,那麽所有石子堆的石子個數的異或和為 $0$。
考慮 $dp[i][j]$ 為前 $i$ 堆石子,異或和為 $j$ 的石子序列總數。題目要求即為 $dp[n][0]$。
另外令 $g[x]=[x\leq m \&\& x\in prime]$,即若 $x$ 為 $m$ 以內的質數則 $g[x]=1$ 否則 $g[x]=0$。
(以下 $\oplus$ 均代表異或)
那麽有邊界:
$dp[1][x]=[g[x]==1]$
考慮到 $j\oplus k\oplus k=j$,那麽轉移方程:
$dp[i][j]=\sum^m_{k=1}dp[i-1][j\oplus k]\quad [g[k]==1]$
這樣暴力轉移復雜度是 $O(Tm^2n)$ 的,考慮優化。
可以發現 $g[x]=[g[x]==1]$,那麽邊界和轉移方程可以化為:
$dp[1][x]=g[x]$
$dp[i][j]=\sum^m_{k=1}dp[i-1][j\oplus k]g[k]$
發現這其實是個多項式異或卷積的形式(因為 $dp[i-1][j\oplus k]g[k]$ 會貢獻到 $dp[i][j]=dp[i][(j\oplus k)\oplus k]$)
那麽用 $FWT$ 優化轉移。時間復雜度優化至 $O(Tmlogmn)$。但還是不夠!
我們發現:
$dp[1][x]=g[x]$,也就是 $dp[1]$ 是 $g$ 本身,即異或卷積意義下的 $g^1$
$dp[2][x]=\sum^m_{k=1}dp[1][x\oplus k]g[k]=\sum^m_{k=1}g[x\oplus k]g[k]$,也就是 $dp[2]$ 是異或卷積意義下的 $g^2$
$\dots\dots$
至於 $dp[3]$ 我推不出來
我們科學證明一下:異或卷積是滿足結合律的,所以若 $dp[i-1]$ 是 $g^{i-1}$,那麽 $dp[i]$ 就是 $dp[i-1]\times g=g^{i-1}*g=g^i$。
所以 $dp[i]=g^i$。
剛剛說了異或卷積滿足結合律,所以可以快速冪加速求 $dp[n]=g^n$,那麽 $dp[n][0]$ 也就求完了,問題迎刃而解。
時間復雜度 $O(Tmlogmlogn)$,還差一點點才能通過。
加一個小優化就行了:在快速冪中乘法要乘很多次,如果每乘完一次就要 $O(mlogm)$ 變換就浪費了。可以一開始 $FWT$,快速冪完之後再 $FWT$,少了很多運算。
這樣就可以用 $O(Tm(logm+logn))$ 通過了。
(p.s:$FWT$ 是對模數沒有要求的,不要被 $10^9+7$ 嚇到了。)
上代碼:
#include<bits/stdc++.h> using namespace std; const int mod=1000000007,inv=500000004; //2的逆元為500000004 int n,m,limit; int a[70070],ans[70070]; int prime[50050],pl; bool vis[50050]; void FWT(int *c,int type){ //模板 for(int mid=1;mid<limit;mid<<=1) for(int r=mid<<1,j=0;j<limit;j+=r) for(int k=0;k<mid;k++){ int x=c[j+k],y=c[j+k+mid]; c[j+k]=(x+y)%mod;c[j+k+mid]=(x-y+mod)%mod; if(type==-1){ c[j+k]=1ll*c[j+k]*inv%mod;c[j+k+mid]=1ll*c[j+k+mid]*inv%mod; } } } void mult(int *a,int *b,int *c){ //點值相乘(為何這裏面沒有FWT?上面說過這會浪費時間) for(int i=0;i<limit;i++) c[i]=1ll*a[i]*b[i]%mod; } void quickpow(int *a,int b){ memset(ans,0,sizeof(ans)); ans[0]=1; //初始化 FWT(a,1);FWT(ans,1); //一開始就變換 while(b){ //快速冪 if(b&1) mult(ans,a,ans); mult(a,a,a); b>>=1; } //中間不變換 FWT(a,-1);FWT(ans,-1); //這時才變換回去 } void init(){ //篩素數,作為轉移數組 vis[1]=true; for(int i=2;i<=50000;i++){ if(!vis[i]) prime[++pl]=i; for(int j=1;j<=pl && i*prime[j]<=50000;j++){ vis[i*prime[j]]=true; if(i%prime[j]==0) break; } } } int main(){ init(); while(~scanf("%d%d",&n,&m)){ memset(a,0,sizeof(a)); for(limit=1;limit<=m;limit<<=1); for(int i=1;i<=pl && prime[i]<=m;i++) a[prime[i]]=1; //轉移數組 quickpow(a,n); printf("%d\n",ans[0]); } }FWT
BZOJ4589 Hard Nim(快速沃爾什變換FWT)