1. 程式人生 > 其它 >洛谷P2490 [SDOI2011]黑白棋(K-NIM)

洛谷P2490 [SDOI2011]黑白棋(K-NIM)

開始漸漸理解博弈
首先提出一個結論:在最優策略下,白棋只會向右走,黑棋只會向左走

證明 :
若白棋向左走,那黑棋可以向左走相同的步數,但此時白棋的活動範圍減少,顯然會劣,黑棋同理

接下來考慮模型轉換
把每一對棋子之間的點數看做石子數,那本題就是經典的 k-NIM 博弈
不妨解釋一下 K-NIM 博弈

K-NIM 博弈

定義:共有 \(n\) 堆石子,每次可以從任意不超過 \(k\) 堆石子中拿走任意石子
在某次無法操作時,該人失敗,遊戲結束

先手必敗:
當且僅當把每堆石子的數量轉換成二進位制後,每個二進位制位上的和 %(k+1) 後均等於零
先手必勝:
只要不滿足上述情況,均為先手必勝局面

解釋一下為什麼膜 \((k+1)\)
考慮多餘的部分,對於 \([0,k]\)
若對面正處於必敗局面,在對面操作後,更改的範圍一定為 \([0,k]\)
那次者則可操作對應的步數 \([k,0]\) 使其重返必敗局面
所以要膜 \((k+1)\)

證明:
首先所有堆石子數量均為0的狀態一定為必敗局面,此時顯然滿足先手必敗
如果存在堆石子數量不為0,但滿足先手必敗局面,在對面操作完後,次者一定可以操作對應的石子數量使其重返必敗局面
假設在修改完其中一堆石子後二進位制最高位不為0的位數為m,那一定可以拿走對應的石子數量使其最高位變為0
因此滿足先手必敗,反之同理

轉移就很好寫了
\(dp_{i,j}\)

表示二進位制前 \(i\) 位膜完後均為 \(0\) ,此時共選了 \(j\) 個石子的方案數
列舉哪幾堆新增石子,則:

\[dp_{i+1,j+(1<<i)*x*(k+1)}+=dp_{i,j}\binom{\frac{m}{2}}{x*(k+1)} \]

因為轉移的堆數是無序的,於是

\[ans+=dp_{maxi,j}\binom{n-i-\frac{m}{2}}{\frac{m}{2}} \]\[ans=\binom{n}{k}-ans \]
Code
#include <bits/stdc++.h>
#define re register
#define int long long
#define pir make_pair
#define fr first 
#define sc second
#define db double
using namespace std;
const int mol=1e9+7;
const int maxn=1e7+10;
inline int qpow(int a,int b) { int ans=1; while(b) { if(b&1) (ans*=a)%=mol; (a*=a)%=mol; b>>=1; } return ans; }
inline int read() {
    int s=0,w=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { s=s*10+ch-'0'; ch=getchar(); }
    return s*w;
}

int n,k,m,fac[10010],inv[10010];
inline int C(int n,int m) { if(n<m) return 0; return fac[n]*inv[m]%mol*inv[n-m]%mol; }
int f[22][100010];
signed main(void) {
    freopen("erp.in","r",stdin); freopen("erp.out","w",stdout);
    n=read(),k=read(),m=read();
    fac[0]=1; for(re int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mol; 
    inv[n]=qpow(fac[n],mol-2); for(re int i=n;i>=1;i--) inv[i-1]=inv[i]*i%mol;
    f[0][0]=1;
    for(re int i=0;i<=20;i++)
        for(re int j=0;j<=n;j++)
            for(re int x=0;j+(1<<i)*x*(m+1)<=n-k&&x*(m+1)<=k/2;x++) {
                (f[i+1][j+(1<<i)*x*(m+1)]+=f[i][j]*C(k/2,x*(m+1))%mol)%=mol;
            } 
    int ans=0;
    for(re int i=0;n-i-k/2>=0;i++) (ans+=f[21][i]*C(n-i-k/2,k/2)%mol)%=mol;
    printf("%lld\n",(C(n,k)-ans+mol)%mol);
}