[VIJOS2055][SDOI2019]移动金币:DP+组合数学

分析

显然可以转化为阶梯nim。

于是问题转化为了对于所有\(i \in [0,n-m]\),求长度为\(\lfloor\frac{m+1}{2}\rfloor\),和为\(i\),异或和非\(0\)的非负整数序列的个数。

直接DP看似不太可行,然而UOJ群的dalao们告诉博主可以按位DP。

\(f[i][j][0/1]\)表示考虑了后\(i\)位,当前的和为\(j\),后\(i\)位的异或和是否为\(0\)的方案数,转移时枚举当前位有多少个\(1\),类似数位DP那样就好。

最后用隔板法统计答案即可。

记搜的话直接记搜可能过不去,加些剪枝就好了。

代码

#include <bits/stdc++.h>

#define rin(i,a,b) for(int i=(a);i<=(b);++i)
#define irin(i,a,b) for(int i=(a);i>=(b);--i)
#define trav(i,a) for(int i=head[a];i;i=e[i].nxt)
#define Size(a) (int)a.size()
#define pb push_back
#define mkpr std::make_pair
#define fi first
#define se second
#define lowbit(a) ((a)&(-(a)))
typedef long long LL;

using std::cerr;
using std::endl;

inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

const int MOD=1e9+9;
const int MAXN=150005;

int n,m,cnt;
int fac[MAXN+50],invf[MAXN+50];
int f[20][MAXN][2];

inline int qpow(int x,int y){
	int ret=1,tt=x%MOD;
	while(y){
		if(y&1)ret=1ll*ret*tt%MOD;
		tt=1ll*tt*tt%MOD;
		y>>=1;
	}
	return ret;
}

inline int binom(int n,int m){
	if(n<0||m<0||n<m)return 0;
	return 1ll*fac[n]*invf[n-m]%MOD*invf[m]%MOD;
}

int dfs(int pos,int sum,int have1){
	if(sum&((1<<pos)-1))return 0;
	if(pos>17){
		if(sum==0&&have1)return 1;
		else return 0;
	}
	if(f[pos][sum][have1]!=-1)return f[pos][sum][have1];
	int ret=0;
	rin(i,0,cnt){
		if((1ll<<pos)*i>sum)break;
		ret=(ret+1ll*dfs(pos+1,sum-(1ll<<pos)*i,have1|(i&1))*binom(cnt,i))%MOD;
	}
	return f[pos][sum][have1]=ret;
}

void init(){
	fac[0]=1;
	rin(i,1,n+m)fac[i]=1ll*fac[i-1]*i%MOD;
	invf[n+m]=qpow(fac[n+m],MOD-2);
	irin(i,n+m-1,0)invf[i]=1ll*invf[i+1]*(i+1)%MOD;
}

int main(){
	memset(f,-1,sizeof f);
	n=read(),m=read();init();
	cnt=(m+1)/2;
	int ans=0,box=(m&1)==0?cnt+1:cnt;
	rin(i,0,n-m){
		int rem=n-m-i;
		ans=(ans+1ll*dfs(0,i,0)*binom(rem+box-1,box-1))%MOD;
	}
	printf("%d\n",ans);
	return 0;
}

posted on 2019-05-08 08:46  ErkkiErkko  阅读(335)  评论(0编辑  收藏  举报