BZOJ 2281: [Sdoi2011]黑白棋 【另类nim游戏】

版权声明:本文为博主原创文章,未经博主允许必须转载。 https://blog.csdn.net/C20181220_xiang_m_y/article/details/88617166

题目传送门

题目分析:

题面似乎有点问题,如果白棋可以往左走,黑棋可以往右走,这游戏可能下不完。
所以白棋只能往右走,黑棋只能往左走。
又因为相邻的棋子颜色不同,所以这就变成了k/2个nim游戏的组合,同时有一点变化,就是每次不是只能从一堆中拿,而是至多d堆。

把每一堆的石子数量看成二进制,那么这个游戏的必败状态就是n堆石子每个二进制位上为1的堆数都是(d+1)的倍数。反之则是必胜态。
最后的状态是必败状态,且每个必胜态都可以变成必败态,而必败态无论怎么拿都会变成必胜态,所以得证。

题目要求必胜方案数,那么用总方案减去必败态,考虑DP分别处理每个二进制位,设 f [ i ] [ j ] f[i][j] 表示处理了前 i i 位,用了 j j 个石子的方案数,那么枚举当前位为1的堆数是(d+1)的几倍来转移:
f [ i + 1 ] [ j + k ( d + 1 ) ( 1 < < i ) ] + = f [ i ] [ j ] C K 2 k ( d + 1 ) f[i+1][j+k(d+1)*(1<<i)]+=f[i][j]*C_{\frac K2}^{k(d+1)}

最后统计答案的时候还要算上每对黑白棋的开始位置在哪里,把每对棋看成一组,去掉 i i 个石子的空,就有:
a n s + = f [ 15 ] [ i ] C n i K 2 K 2 ans+=f[15][i]*C_{n-i-\frac K2}^{\frac K2}

总方案就是 C n K C_n^K ,减去ans就是最终答案。

Code:

#include<cstdio>
#include<cctype>
#include<algorithm>
#define maxn 100005
using namespace std;
inline void read(int &a){
    char c;while(!isdigit(c=getchar()));
    for(a=c-'0';isdigit(c=getchar());a=a*10+c-'0');
}
const int mod = 1e9+7;
int n,K,d,f[17][10005],c[10005][105];
inline void add(int &a,int b){a+=b;if(a>mod) a-=mod;}
int main()
{
    read(n),read(K),read(d),K>>=1;//先把K除2
    c[0][0]=1;
    for(int i=1;i<=n;i++){
        c[i][0]=1;//这里j的循环注意取min
        for(int j=min(i,2*K);j>=1;j--) c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
    }
    f[0][0]=1;
    for(int i=0,bit=d+1;i<=14;i++,bit<<=1)//这里的bit直接从d+1开始
        for(int j=n-2*K;j>=0;j--) if(f[i][j])
            for(int k=min(K/(d+1),(n-2*K-j)/bit);k>=0;k--)
                add(f[i+1][j+k*bit],1ll*f[i][j]*c[K][k*(d+1)]%mod);
    int s=0;
    for(int i=0;i<=n-2*K;i++) add(s,1ll*f[15][i]*c[n-i-K][K]%mod);
    printf("%d",(c[n][2*K]-s+mod)%mod);
}

猜你喜欢

转载自blog.csdn.net/C20181220_xiang_m_y/article/details/88617166
今日推荐