uva11758 解题报告(神博弈之一)

题目链接 https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2858

题意:给你一个一维的棋盘,大小为n,然后给你k个棋子,k一定为偶数,有k/2个灰棋子,k/2个白棋子,灰色和白色一定成对摆放(先灰后白)然后两个人,A,B,每次可以进行一种操作,可以选择其中d个棋子(A只能选灰,B只能选白)让这个棋子左移或者右移任意步(至少要选择一个棋子移动一步)问你一共有多少种摆放方式,使得先手必胜

解析:
分析之,灰色棋子向左是无意义的,(后手进行相同的推进操作,局面不变)同理,白色棋子向右也无意义。所以我们只考虑灰色向右和白色向左。那么这样看来,每对棋子就是一个nim游戏。
不过因为先手可以选择d堆进行操作,求先手必胜的状态必然复杂度很高,那么反之,用总的摆法减去后手必胜就是先手必胜。
对于这样K堆的nim,每次选择1-d堆进行操作,有一个定理,就是a1,a2,a3...ak的每位二进制的和一定要是(d+1)的倍数(可以为0)这样就是后手必胜的条件。
至于为什么这样,简单说说,不严格证明。
假设a1,a2,a3...ak的二进制位写开(每个数占一列,从最低位到第18位(一般18即可,不够再加)) 对于每行都满足1的个数为d+1的倍数,我们称这个状态为平衡态。先手遇到此平衡态必败。如果先手随意破坏此平衡态,对于后手来说,因为也可以选至多d个数,然后再把先手每拿掉的一些位上的1拿掉即可(这样比较抽象,可以这么理解,就是10个糖果,最多拿d个最少拿一个,当然必胜策略就是你的对手拿了a个,你就拿d+1-a个不就行了(平衡态后手必胜策略))
明白了这个结论之后,假设a1,a2,...,ak都已经确定,那么没确定的就是他的摆放位置,和a1,a2...ak多大没有半毛钱关系。然后这道题就成了一道dp计数的问题(类似数位dp)dp[i][j]表示前行,权和为j的个数)一个数位的背包就这样了。枚举这一行1的个数m满足d+1|m,进行一次选择就行了,最后再计算这样的a1-ak有多少种摆法(把每堆打包插入,排列组合)
算法就是酱紫
//
//  Created by Matrix on 2015-11-26
//  Copyright (c) 2015 Matrix. All rights reserved.
//
//
//#pragma comment(linker, "/STACK:102400000,102400000")
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <sstream>
#include <set>
#include <vector>
#include <stack>
#define ALL(x) x.begin(), x.end()
#define INS(x) inserter(x, x,begin())
#define ll long long
#define CLR(x) memset(x, 0, sizeof x)
using namespace std;
const int inf = 0x3f3f3f3f;
const ll MOD = 1e9 + 7;
const int maxn = 1e2 + 10;
const int maxv = 1e3 + 10;
const double eps = 1e-9;

int t;
int n, k, d;
ll c[maxn * 100][maxn];
ll dp[20][maxv * 10];
int main() {
#ifdef LOCAL
    freopen("in.txt", "r", stdin);
//  freopen("out.txt","w",stdout);
#endif
    scanf("%d", &t);
    for(int i = 1; i <= 10000; i++) {
        c[i][0] = c[i][i] = 1;
        for(int j = 1; j < min(103, i); j++) {
            c[i][j] = (c[i-1][j] + c[i-1][j-1]) % MOD;
        }
    }
    while(t--) {
        scanf("%d%d%d", &n, &k, &d);
        k /= 2;
        CLR(dp);
        dp[0][0] = 1;
        for(int i = 0; i < 15; i++) {
            for(int j = 0; j * (1 << i) <= n && j <= k; j += d + 1) {
                int num = j * (1 << i);
                for(int w = n - 2 * k; w >= num; w--) {
                    dp[i+1][w] = (dp[i+1][w] + dp[i][w - num] * c[k][j]) % MOD;
                }
            }
        }
        ll ans = c[n][2*k];
//      printf("ans = %lld\n", ans);
        for(int i = 0; i <= n - 2 * k; i++) {
//          printf("dp[%d] = %lld\n", i, dp[15][i]);
            ll res = c[n-i-k][k] * dp[15][i] % MOD;
//          printf("res[%d] = %lld\n", i, res);
            ans = (ans - res + MOD) % MOD;
        }
        cout << ans << endl;
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值