[BZOJ2281][Sdoi2011]黑白棋 && 博弈+计数

来源:互联网 发布:美军最先进航母数据 编辑:程序博客网 时间:2024/06/05 01:19

一开始看题目看了好久结果最后才发现相邻棋子颜色不同



既然相邻棋子颜色不同 那么就一定能决出胜负 所以白棋左移 黑棋右移毫无意义 所以这个问题就神奇的转化为了NIMK游戏


两个相邻棋子之间的空格视为石子 每个人每次可以从d堆石子中拿出石子 那么先手必败当且仅当石子的NIM和中1的个数为d+1的倍数 

显然终态为0 那么若有d+1个1 先手一次性不能把该状态直接转移到0而且至少要进行一次操作 所以后手一定可以把先手操作之后的状态转移到0 因此先手必败


那么问题变成了求先手必败的情况总数 

我们用f[i][j]表示NIM和前i为中有j个1的先手必败的方案数 每次枚举一个d+1的倍数进行转移 转移如下

f[i+1][j+k*(d+1)*(1<<i)] += f[i][j] * C(K/2,  k*(d+1))

(这一次使用的石子数为 k*(d+1)*(1<<i), 乘上C(K/2,  k*(d+1))是因为有K/2堆石子 要选出k*(d+1)堆)

最后的答案为 C(n, 2*k) - sigema( f[15][i] * C(n-i-K/2, K/2) |  0 <= i <= n-2*K)

最后乘上组合数的原因是尽管我们构造了每一堆的数量 但是没有构造每一堆的位置

因为每一堆两个端点的位置确定了 所以可以自由选择的位置就只有n-i-K/2个


至此本题就可以解决了 

(结果不仅看错题,还数组忘记开缓存WA了,果然像我这样的人最好早点滚粗


#include<cstdio>#include<algorithm>#include<cstring>#include<iostream>#include<queue>#define SF scanf#define PF printfusing namespace std;typedef long long LL;const int MAXN = 10000;const int MAXK = 100;const int MOD = 1000000007;int n, K, d;LL c[MAXN+10][MAXK*2+10], f[15+10][MAXN+10], ans, tot;void init() {    for(int i = 0; i <= MAXN; i++) c[i][0] = 1;    for(int i = 1; i <= MAXN; i++)        for(int j = 1; j <= min(i, MAXK*2); j++)            c[i][j] = (c[i-1][j-1] + c[i-1][j]) % MOD;}LL C(int n, int k) {    if(k > n - k) k = n-k;    return c[n][k];}int main() {    init();    SF("%d%d%d", &n, &K, &d);    K >>= 1;    f[0][0] = 1;    for(int i = 0; i < 15; i++)        for(int j = 0; j <= n - 2 * K; j++)            for(int k = 0; k * (d+1) <= K && j + k * (d+1) * (1 << i) <= n - 2*K; k++)                f[i+1][j+k*(d+1)*(1<<i)] = (f[i+1][j+k*(d+1)*(1<<i)] + f[i][j] * C(K, k*(d+1)) % MOD) % MOD;    for(int i = 0; i <= n - 2*K; i++) ans = (ans + f[15][i] * C(n - i - K, K) % MOD) % MOD;    tot = C(n, 2*K);    cout << (tot - ans + MOD) % MOD;    return 0;}



0 0
原创粉丝点击