1. 程式人生 > >bzoj 2281: [Sdoi2011]黑白棋 博弈論+動態規劃+排列組合

bzoj 2281: [Sdoi2011]黑白棋 博弈論+動態規劃+排列組合

題意

小A和小B又想到了一個新的遊戲。
這個遊戲是在一個1*n的棋盤上進行的,棋盤上有k個棋子,一半是黑色,一半是白色。
最左邊是白色棋子,最右邊是黑色棋子,相鄰的棋子顏色不同。
小A可以移動白色棋子,小B可以移動黑色的棋子,他們每次操作可以移動1到d個棋子。
每當移動某一個棋子時,這個棋子不能跨越兩邊的棋子,當然也不可以出界。當誰不可以操作時,誰就失敗了。
小A和小B輪流操作,現在小A先移動,有多少種初始棋子的佈局會使他勝利呢?
1<=d<=k<=n<=10000, k為偶數,k<=100。
答案對1000000007取模。

分析

顯然先手的必敗局面就是每一對棋子都靠一起,這樣的話無論先手怎麼動,後手只要跟著模仿,最後先手總會沒有路走。
那麼我們可以把每對棋子之間的空格子看作是石子,那麼這就轉換成了一個拓展版的Nim遊戲:有n堆石子,每次可以從其中d堆中任意取若干個石子,不能取的一方算輸。
而這個遊戲先手必敗當且僅當把n堆石子的數量全部轉化為二進位制後,每一位的1的個數都是(d+1)的倍數,否則必勝。證明比較複雜,可以

點這裡

有了這個結論這題就好做了。
我們從二進位制低位往高位dp。設f[i,j]表示dp到第i位,當前總共放了j顆石子的必敗局面方案數。那麼f[i,j]=f[i1,jl(d+1)2i]Cl(d+1)k/2

那麼答案就等於Cknnki=0f[w,i]Ck/2nk/2i

程式碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;

const int
N=10005; const int MOD=1000000007; int n,k,d,f[20][N],bin[20],ny[N],jc[N]; int ksm(int x,int y) { int ans=1; while (y) { if (y&1) ans=(LL)ans*x%MOD; x=(LL)x*x%MOD;y>>=1; } return ans; } int C(int n,int m) { return (LL)jc[n]*ny[m]%MOD*ny[n-m]%MOD; } int main() { scanf("%d
%d%d"
,&n,&k,&d); bin[0]=1; for (int i=1;i<=16;i++) bin[i]=bin[i-1]*2; jc[0]=ny[0]=1; for (int i=1;i<=n;i++) jc[i]=(LL)jc[i-1]*i%MOD,ny[i]=ksm(jc[i],MOD-2); f[0][0]=1; for (int i=1;i<=15;i++) for (int j=0;j<=n-k;j++) for (int l=0;l*(d+1)<=k/2&&l*(d+1)*bin[i-1]<=j;l++) (f[i][j]+=(LL)f[i-1][j-l*(d+1)*bin[i-1]]*C(k/2,l*(d+1))%MOD)%=MOD; int ans=0; for (int i=0;i<=n-k;i++) (ans+=(LL)f[15][i]*C(n-k/2-i,k/2)%MOD)%=MOD; ans=(C(n,k)-ans+MOD)%MOD; printf("%d",ans); return 0; }