P5363 [SDOI2019]移动金币

拆盒子+计数dp
既然运动和静止是相对的 我们可以把 移动金币转化成向另一个方向移动空盒子
这样的好处是 把金币看成隔板 不会出现一堆金币卡在一边不好处理的情况
而且可以转化成一个阶梯nim游戏 即每个台阶上有一些石子 只能往下移动一些石子 问先手必胜的条件
一个结论是 当奇数台阶上石子异或和为0时先手必败
类似普通nim游戏,但如果对手操作了偶数台阶上的石子,我们总能通过同样的操作把石子放回偶数台阶
而奇数没有这个性质,因为到底了就不能动了
现在问题转化成了在m+1个台阶上塞n-m个石子,保证奇数异或和非0的方案数
显然要容斥,求异或和为0的方案数
一个简单的思路是前i个台阶放了j个石子异或和为k 但复杂度爆炸
考虑按位计算贡献,枚举第i位放了k个1,剩下的和为j,k为偶数
把k个1塞进去可以组合数计算方案
然后现在考虑的全是奇数阶梯,还剩下偶数台阶没有算
就在统计答案的时候把f[max][i]剩下的i个石头插板法算一算方案数乘进去分配给偶数阶梯就可以了

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<cstring>
using namespace std;
const int inf=0x7fffffff;
typedef long long ll;
#define maxn 160009
#define int ll
int n,m;
int f[50][150009];
int Fac[maxn];
int invFac[maxn];//拒绝阴间变量名 
#define mod 1000000009
int ksm(int a,int b)
{
	int base=a,res=1;
	while(b)
	{
		if(b&1)
		{
			res=(res*base)%mod;
		}
		base=(base*base)%mod;
		b>>=1;
	}
	return res;
}
void Init(int to)
{
	Fac[0]=1;
	for(int i=1;i<=to;i++)
	{
		Fac[i]=(Fac[i-1]*i)%mod;
	}
	invFac[to]=ksm(Fac[to],mod-2);
	for(int i=to-1;i>=0;i--)
	{
		invFac[i]=(invFac[i+1]*(i+1))%mod;
	}
}
int C(int n,int m)
{
	if(n<m||m<0)return 0;
	int res=Fac[n];
	res=((res*invFac[m])%mod*invFac[n-m])%mod;
	return res;
}
int Chaban(int x,int y)
{
	if(x==0&&y==0)return 1;
	return C(x+y-1,y-1);
}
signed main()
{
	scanf("%lld%lld",&n,&m);
	if(n<m)
	{
		printf("0");
		return 0;
	}
	Init(n+m);
	int k = 1;
	while ((1 << k) <= n) k++;
	f[k][n-m]=1;
	for(int i=k-1;i>=0;i--)
	{
		for(int j=0;j<=n-m;j++)
		{
			if(!f[i+1][j])continue;
			for(int k=0;k<=(m+1)/2&&(1<<i)*k<=j;k+=2)
			{
				f[i][j-k*(1<<i)]=(f[i][j-k*(1<<i)]+C((m+1)/2,k)*f[i+1][j])%mod;
			}
		}
	}
	ll ans=0;
	for(int i=0;i<=n-m;i++)ans=(ans+f[0][i]*Chaban(i,(m)/2+1))%mod;
	printf("%lld\n",(C(n,m)-ans+mod)%mod);
	return 0;
}
/*
题目转化为 Cnm 减去 
一共有n-m个球 放到m+1个盒子里 可以不放 
但是奇数位异或和为0 
记g(i,j)表示从高到低考虑到了前i位,当前所有数的和还剩j的方案数。

*/ 
posted @ 2022-07-20 09:32  lzylzy/kk  阅读(46)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end