JZOJ2368 【SDOI2011】黑白棋
阿新 • • 發佈:2018-12-29
題目
題目大意
在一個1*n的棋盤上,有黑棋和白棋交錯分佈,每次,一個人可以移動自己的
顆旗子。
問先手必勝的方案數。
思考歷程
在一開始,我就有點要放棄的念頭。
因為這題是一道博弈問題。
我是非常不擅長博弈類問題的。
但是其他的題有想不出來,於是只能硬是想了好久。
最終那了個部分分。
正解
這題正解當然跟博弈有一些關係。
首先,對於這題有一個隱藏的限制。
白棋不能向左移,黑棋不能向右移。
這是為什麼?
我們可以感性理解一下(證明什麼的我當然不會):
如果白棋左移,那麼在它右邊的黑棋可以模仿它的操作,也跟著左移。
同理,如果黑棋右移,那麼在它左邊的白棋也可以模仿他的動作,也跟著右移。
那麼我們就可以發現,這樣走是沒有意義的。
然後我們就可以轉換問題的模型:
可以將對應(相鄰)的兩個白棋和黑棋中間的距離當作石子。
那麼就有 堆石子,然後每次可以在至多 堆石子,至少 堆石子中各自拿走一顆石子。
這個問題叫Nimk問題。
對於Nimk問題,我們有一個神奇的結論:將所有堆石子的個數化成二進位制,每一位統計一下 的個數,如果個數都是 的倍數,那麼這就是必敗態。
如何證明?
我們可以感性理解一下:
先手只能拿走 到 堆中的石子,那麼,後手可以故意的模仿,然後將兩人取得的石子的總數固定在 。那麼如果一直這麼下去,後手必定會贏。
為什麼要轉成二進位制呢?
我覺得,其實只要轉成了二進位制,那麼後手就能保證每次能夠取得這麼多的石子,使得兩個人取石子的總數為 。具體怎樣解釋,我覺得我還要好好理解一下,暫且感性感性吧(感性理解真是一個好東西)。
模型轉換成了Nimk問題,讓我們求先手必勝的方案數。
先手必勝的方案數等於總方案數減去先手必敗的方案數。
如何求先手必敗的方案數呢?
當然是DP。
設
表示在二進位制的前
位中,一共用了
顆石子的先手必敗的方案數。
仔細想想這個狀態沒有任何問題,因為我們只需要保證在每一個二進位制位上都滿足它必敗就好了。這樣設狀態之後轉移就方便多了。
我們再列舉一個
(小寫的
,不要混淆),表示一共有
堆石子下一個二進位制位上為
。
轉移?
注意:為了程式實現方便,在這個方程中,前
位的最高位實際上是
位。這樣的DP中,初始化直接是
在一波DP之後,我們就可以統計答案了。
其實我們可以將其視為,在
個空中插入
個東西。
這個東西可以用組合數來搞一搞,那麼就是
種方案,用它來乘上
就好了。
然後這題就愉快地解決了。
複雜度表示懶得分析。
程式碼
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define mo 1000000007
int n,K,d;
int C[10001][101];
int f[17][10001];
int main(){
scanf("%d%d%d",&n,&K,&d);
C[0][0]=1;
for (int i=1;i<=n;++i){
C[i][0]=1;
for (int j=1;j<=i && j<=K;++j)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mo;
}
f[0][0]=1;
for (int i=0;i<15;++i)
for (int j=0;j<=n-K;++j)
if (f[i][j])
for (int k=0;k*(d+1)<=K>>1 && j+k*(d+1)*(1<<i)<=n-K;++k)
(f[i+1][j+k*(d+1)*(1<<i)]+=(long long)f[i][j]*C[K>>1][k*(d+1)]%mo)%=mo;
int ans=0;
for (int j=0;j<=n-K;++j)
ans=(ans+(long long)f[15][j]*C[n-j-(K>>1)][K>>1]%mo)%mo;
printf("%d\n",(C[n][K]-ans+mo)%mo);
return 0;
}
我認為這個程式實現不用註釋。
總結
博弈類問題,可真是一個神奇東西啊!
然而,好多的我都不會嚴謹證明。
感性理解就好……
我覺得以後要多做一些博弈類問題。