码迷,mamicode.com
首页 > 其他好文 > 详细

[SDOI2011]黑白棋

时间:2019-03-08 09:55:33      阅读:186      评论:0      收藏:0      [点我收藏+]

标签:cstring   sdoi2011   amp   pre   组合   答案   个人   一半   不同的   

题目描述

小A和小B又想到了一个新的游戏。

这个游戏是在一个1*n的棋盘上进行的,棋盘上有k个棋子,一半是黑色,一半是白色。

最左边是白色棋子,最右边是黑色棋子,相邻的棋子颜色不同。

小A可以移动白色棋子,小B可以移动黑色的棋子,他们每次操作可以移动1到d个棋子。

每当移动某一个棋子时,这个棋子不能跨越两边的棋子,当然也不可以出界。当谁不可以操作时,谁就失败了。

小A和小B轮流操作,现在小A先移动,有多少种初始棋子的布局会使他胜利呢?

输入输出格式

输入格式:

共一行,三个数,n,k,d。

输出格式:

输出小A胜利的方案总数。答案对1000000007取模。

输入输出样例

输入样例#1:

10 4 2

输出样例#1:

182


题解

组合数学+\(niimK\)游戏

以前没具体学过博弈论==
先扯一下\(nimK\)游戏是个啥东西:

\(nimK\)游戏
\(nimK\)游戏与\(nim\)游戏类似,就是给你n堆石子,最后那个不能取石子的人输,与常规\(nim\)游戏不同的是每个人一次可以从\(K\)堆中取若干棋子

必败态就是对于二进制下的每一位,所有堆石子在二进制下的该位的和能被\((K+1)\)整除(可以类比\(nim\)游戏,或者也可以说成是\(nim2\)游戏\(?\)总之就是我不会证

然后看这个题
好像题面没有给全,就是黑棋只能往左,白棋只能往右,黑白棋子必须相间
所以就可以把这\(k/2\)对左边是黑棋右边是白棋之间的空隙看做是每堆石子
那么就成了\(nimK\)游戏求先手必胜态
先手必胜不好求,考虑先手必败态再用总方案数减去先手必胜态即可
那就设\(f[i][j]\)表示前i位已经满足条件(即所有堆石子的二进制这一位之和能被\(K+1\)整除)
dp即可

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
const int M = 10005 ;
const int N = 20 ;
const int mod = 1e9 + 7 ;
using namespace std ;

int n , m , k , w ;
int ans , f[N][M] , fac[M] ;

inline int Fpw(int Base , int k) {
    int temp = 1 ;
    while(k) {
        if(k & 1) temp = 1LL * temp * Base % mod ;
        Base = 1LL * Base * Base % mod ; k >>= 1 ;
    }
    return temp ;
}
inline int C(int n , int m) {
    return 1LL * fac[n] * Fpw(fac[m] , mod - 2) % mod * Fpw(fac[n - m] , mod - 2) % mod ;
}
/*
f[i][j] 表示已经考虑了前i位,前i位的异或和都是0了,这样已经花掉了j的空间 
你现在有m-1堆,每一位的二进制位必须是k的倍数
你一共能分配的点数是n-m 
*/
int main() {
    scanf("%d%d%d",&n,&m,&k) ;
    fac[0] = 1 ; f[0][0] = 1 ; ++ k ;
    for(int i = 1 ; i <= n ; i ++) 
        fac[i] = 1LL * fac[i - 1] * i % mod ;
    for(int i = 0 ; i <= 14 ; i ++) {
        for(int j = 0 ; j <= n - m ; j ++) {
            if(!f[i][j]) continue ;
//  现在来枚举这一位上有几个堆去放东西
            for(int t = 0 ; t <= m / 2 ; t += k) {
                if(j + (1 << i) * t > n - m) break ;
                f[i + 1][j + (1 << i) * t] = (f[i + 1][j + (1 << i) * t] + 1LL * f[i][j] * C(m / 2 , t) % mod) % mod ;
            }
        }
    }
    ans = C(n , m) ;
//  把一对黑白点看做一个点
    for(int i = 0 ; i <= n - m ; i ++) 
        ans = ((ans - 1LL * f[15][i] * C( n - m / 2 - i , m / 2 ) % mod) % mod + mod) % mod ;
    printf("%d\n",ans) ;
    return 0 ;
}

[SDOI2011]黑白棋

标签:cstring   sdoi2011   amp   pre   组合   答案   个人   一半   不同的   

原文地址:https://www.cnblogs.com/beretty/p/10493650.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!