【bzoj2281】黑白棋 博弈论

       这道题目的转化真是巧妙啊。不过据说题目有点问题,那就不管了。首先将第i个白子和第i个黑子组成一对。那么这一对中,白子的左移是没有意义的,黑子的右移也是没有意义的(虽然有反例,但好像只能这样了)。那么每一对黑子和白子可以看成一堆火柴,则问题转化为一次在d堆中拿火柴的必胜策论。

       对于d-Nim(就这么叫了)问题,有个结论是如果对于二进制的每一位的抑或值x都有x≡0(mod d+1),那么为必败状态,否则为必胜状态。证明不会。。

       然后就可以用f[i][j]表示到二进制的第i位(从低到高),目前总和为j的必败状态方案数。注意j<=N-K。那么f[i][j]可以转移到f[i+1][j+x*(d+1)*2^(i)],注意有C(K/2,x*(d+1))种组合方式

       最后统计必胜状态。用所有的状态C(N,K)减去所有的必败状态。在必败状态中,如果两端的点都确定了,那么j就确定了。不妨枚举这个j,然后在组合一下点的排列方式即可。

AC代码如下:

#include<iostream>
#include<cstdio>
#define ll long long
#define mod 1000000007
using namespace std;
 
int n,m,d,bin[25];
ll c[20005][205],f[25][20005];
ll C(int aa,int bb){
    if (bb*2>aa) return c[aa][aa-bb]; else return c[aa][bb];
}
int main(){
    scanf("%d%d%d",&n,&m,&d); int i,j;
    bin[0]=1; for (i=1; i<=15; i++) bin[i]=(bin[i-1]<<1)%mod;
    for (i=0; i<=n; i++){
        c[i][0]=1;
        for (j=1; j<=m && j<=i; j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
    }
    f[0][0]=1;
    for (i=0; i<15; i++)
        for (j=0; j<=n-m; j++)
            for (int k=0; k*(d+1)<=m/2; k++){
                int x=j+k*(d+1)*bin[i]; if (x>n-m) break;
                f[i+1][x]=(f[i+1][x]+f[i][j]*C(m/2,k*(d+1)))%mod;
            }
    ll ans=0;
    for (i=0; i<=n-m; i++) ans=(ans+f[15][i]*C(n-m/2-i,m/2))%mod;
    printf("%lld\n",(C(n,m)-ans+mod)%mod);
    return 0;
}

by lych

2015.12.5

你可能感兴趣的:(组合,博弈论)