【洛谷5363】[SDOI2019] 移动金币(动态规划)

点此看题面

大致题意:(n)个格子,让你摆放(m)个金币。二人博弈,每次选择一个金币向左移任意格,无法移动者输。问有多少种方案使先手必胜。

阶梯(Nim)

阶梯(Nim)的基本模型,就是有(n)层楼梯(从(0sim n-1)编号),每层楼梯上有若干石子,每次可以取任一层楼梯上任意多个石子到下一层,无法移动者输。

它的解决方法就是,去掉所有编号为偶数的楼梯,然后对剩下的这些编号为奇数的楼梯当成普通(Nim)来做。

原理是,如果一人移动编号为偶数的楼梯上的石子到下一层,如移动第(2n)层的(a)个石子到第(2n-1)层,那么无论何时对方都可以把这(a)个石子接着从第(2n-1)层移动到第(2n-2)层,因为(2n-2)层是必然存在的。

所以,移动偶数层的石子相当于是无效的,就可以直接忽略。

此题转化

对于这道题,我们可以发现,如果把每两个金币之间的空格当作一堆石子,这就是一个典型的阶梯(Nim)

也就是说,若要先手必胜,就要满足奇数堆石子个数异或值不为(0)

这显然不好求,所以我们可以转而求异或值为(0)的方案数,再用总方案数(C_n^m)减去它即为答案。

动态规划

考虑如何求异或值为(0)的方案数。

可以在二进制下逐位(DP)

我们设(f_{i,j})表示满足二进制下从最高位至右数第(i)位异或值为(0),剩余石子总数为(j)的方案数

那么对于(f_{i,j})的转移,我们可以枚举有(k)个奇数堆石子个数二进制下第(i)位为(1),其中因为要使异或值为(0),因此(k)为偶数。

转移方程为:

[f_{i,j}=sum C_{t1}^k imes f_{i+1,j+k imes2^i} ]

其中(t1)表示奇数堆的个数。

计算答案

我们可以枚举满足所有奇数堆每一位异或值为(0)时所剩的石子个数(i),这就是偶数堆的石子个数。

则用插板法就可以求出异或值为(0)的方案数为:

[sum_{i=1}^{n-m}C_{i+t0-1}^{t0-1} imes f_{0,i} ]

其中(t0)表示偶数堆的个数。

最后用(C_n^m)减去这一方案数就是答案。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int 
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 150000
#define M 50
#define LN 20
#define X 1000000009
#define Qinv(x) Qpow(x,X-2)
#define C(x,y) (1LL*Fac[x]*IFac[y]%X*IFac[(x)-(y)]%X)//组合数
using namespace std;
int n,m,Fac[N+5],IFac[N+5],f[LN+5][N+5];
I int Qpow(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
int main()
{
	RI i,j,k,t0,t1,res=0;scanf("%d%d",&n,&m),t0=(m>>1)+1,t1=m+1>>1;//计算偶数堆和奇数堆个数
	for(k=max(n,2*m),Fac[0]=i=1;i<=k;++i) Fac[i]=1LL*Fac[i-1]*i%X;//初始化阶乘
	for(IFac[k]=Qinv(Fac[k]),i=k-1;~i;--i) IFac[i]=1LL*IFac[i+1]*(i+1)%X;//初始化阶乘逆元
	for(f[LN][n-m]=1,i=LN-1;~i;--i) for(j=0;j<=n-m;++j)//动态规划
		for(k=0;k<=t1&&j+(k<<i)<=n-m;k+=2) f[i][j]=(C(t1,k)*f[i+1][j+(k<<i)]+f[i][j])%X;
	for(i=0;i<=n-m;++i) res=(C(i+t0-1,t0-1)*f[0][i]+res)%X;//计算异或值为0的方案数
	return printf("%d",(C(n,m)-res+X)%X),0;//输出答案
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5363.html