SDOI2019移动金币(博弈论)

题目链接:SDOI2019移动金币

题意简述:在一个\(1*n\)的棋盘上,有\(m\)个棋子.小\(A\),小\(B\)每次可以把一个棋子向左移动若干格,但不能越过其他棋子.第一个无法操作的人输.问给定\(n,m\),有多少种局面使得\(小A\)必胜

\(n \leq 150000,m \leq 50\)

solution

  • 首先转化题意,我们不考虑棋子,我们考虑棋子移动转移的空格.可以发现原题等价于有若干个阶梯,除了第0级阶梯外,你可以讲若干个石子从一个阶梯移到上一个阶梯,不能操作的输.初始时棋子间的空格就是石子的数量.
  • 这个典型的阶梯博弈问题.这里给出结论,仅考虑奇数台阶的石子,若异或和为0必败,反之必胜.
  • proof:

  • 先手操作每一步后手显然可以跟着模仿,将其操作至下一个阶梯.于是偶数阶梯的石子可以认为不存在.奇数阶梯的石子等价于每次取若干个直接删掉.于是按照\(nim\)游戏的结论便能证明.
  • 有了这个结论我们可以直接考虑\(dp\),考虑计算必败的方案.设\(dp[bit][s]\)表示当前\(dp\)\(bit\)位,之前每一位异或和都为\(0\),还剩\(s\)个棋子的方案数.然后我们考虑奇数石子有几堆,答案是\(\lceil \frac{m}{2} \rceil\),(每一堆允许有空),总个数会\(\leq n - m\),所以答案是\(\sum_{i = 1}^{n-m} dp[19][i] * (偶数的石子分成若干堆的方案数)(\log{150000} \leq 19)\).每次转移枚举有多少个该位是\(1\)即可.时间复杂度\(O(n*m*\log n)\)
  • 有一个坑点,就是在这个游戏第\(0\)级台阶是可能初始时就有石子的,虽然不影响胜负但是会影响方案数.
/*[SDOI2019]移动金币*/
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int read(){
	char c = getchar();
	int x = 0;
	while(c < '0' || c > '9')		c = getchar();
	while(c >= '0' && c <= '9')		x = x * 10 + c - 48,c = getchar();
	return x;
}
int n,m;
int t;
const int N = 3e5 + 10;
int dp[21][N];
int fac[N],invfac[N];
const int mod = 1e9 + 9;
#define T 19
int qpow(int x,int y){
	int ans = 1;
	while(y){
		if(y & 1)	ans = 1ll * ans * x % mod;
		x = 1ll * x * x % mod;
		y >>= 1;
	}
	return ans;
}
void add(int &x,int y){
	x += y - mod;
	x += (x >> 31) & mod;
}
int C(int x,int y){
	return 1ll * fac[x] * invfac[y] % mod * invfac[x-y] % mod;
}
int calc(int bit,int s){
	if(bit == -1){
		if(s == 0)	return 1;
		return 0;
	}
	if(s < 0) 	return 0;
	if(dp[bit][s] != -1)	return dp[bit][s];
	dp[bit][s] = 0;
	for(int i = 0; i <= t; i += 2){
		int v = (1 << bit) * i;
		add(dp[bit][s],1ll * C(t,i) * calc(bit-1,s-v) % mod);
	}
	return dp[bit][s];
}
int main(){
	n = read(),m = read();
	t = ((m - 1) / 2) + 1;
	memset(dp,-1,sizeof(dp));
	int ans = 0;
	fac[0] = 1;
	for(int i = 1; i <= (n << 1); ++i)	fac[i] = 1ll * fac[i-1] * i % mod;
	for(int i = 0; i <= (n << 1); ++i)	invfac[i] = qpow(fac[i],mod-2);
	int o = m + 1 - t;
	for(int i = 0; i <= n - m; ++i){
		int cnto = n - m - i;
		add(ans,1ll * calc(T,i) * C(cnto + o - 1,o - 1) % mod);
	}
	ans = (1ll * fac[n] * invfac[m] % mod * invfac[n-m] % mod - ans + mod) % mod;
	printf("%d\n",ans);
	return 0;
}
posted @ 2021-03-14 16:40  y_dove  阅读(112)  评论(0编辑  收藏  举报