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

8 篇文章 0 订阅

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



既然相邻棋子颜色不同 那么就一定能决出胜负 所以白棋左移 黑棋右移毫无意义 所以这个问题就神奇的转化为了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 printf
using 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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值