HDU5201-The Monkey King

题目

题目大意

简化题意:1~n个不同盒子,放入m个相同小球,盒子可空,但是1号盒子的球数必须严格最多,问合法方案数。

分析

简化问题

如果没有1号盒子严格最多的限制,就变成了一个插板的问题。

也就是x1+x2+xn=mx_1+x_2……+x_n=m这类不定方程求非负整数解的问题,插板法解决。

容斥

如果枚举猴王(1号盒子)的球数为i

那么问题就变成不定方程并且0<=x<=i0<=x<=i,加了一个上限。

上限通常会用容斥转化成下限,下限继续插板解决。

枚举至少k个盒子的球数>=i>=i,然后容斥解决问题。

假设现在是要求至少k个盒子的球数不少于1号盒子,那么就先选出k个盒子,也就是Cm1kC_{m-1}^{k}

然后向每个选出来的盒子放i个球,剩下的n(k+1)in-(k+1)*i个球随便放入m1m-1个盒子里面

然后运用插板法,Cn(k+1)i+m2m2C_{n-(k+1)*i+m-2}^{m-2}种方法。

子问题得证

现在我们解决了已知1号盒子球数i、至少k个盒子的球数不小于i的方案个数。

然后就用容斥解决。

容斥其实和二项式反演有关,有的时候容斥不太好理解或者好想到,就先想到二项式反演再得到容斥的式子。

这里就先给出容斥的形式,二项式反演后文会讲解。

确定i的时候,令f(k)f(k)表示至少k个盒子球数不少于i的方案数

f(k)=Cm1kCn(k+1)i+m2m2f(k)=C_{m-1}^{k}*C_{n-(k+1)*i+m-2}^{m-2}

那么对于i,答案为:k=0n(k+1)i>=0(1)kf(k)\sum_{k=0}^{n-(k+1)*i>=0} {(-1)^{k}f(k)}

相关代码

for(int i=1;i<=n;i++){//枚举猴王得到的桃子个数 
			long long id=1;
			for(int k=0;n-(k+1)*i>=0;k++){//枚举至少有k只小猴子得到的桃子个数>猴王 
				ans+=id*C(m-1,k)%mod*C(n-(k+1)*i+m-2,m-2)%mod;
				ans%=mod;
				id=-id;
			}
		}

代码

下附AC代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int read(){
	char s;
	int x=0,f=1;
	s=getchar();
	while(s<'0'||s>'9'){
		if(s=='-')f=-1;
		s=getchar();
	}
	while(s>='0'&&s<='9'){
		x*=10;
		x+=s-'0';
		s=getchar();
	}
	return x*f;
}
const long long mod=1000000007;
const int N=300000;
long long qpow(long long a,long long b){
	if(b==0)return 1;
	long long rec=qpow(a,b/2)%mod;
	if(b&1)return rec*rec%mod*a%mod;
	return rec*rec%mod;
}
long long calc[N],inv[N];//阶乘 阶乘逆元 
void init(int n){
	calc[0]=1;
	for(long long i=1;i<=n;i++){
		calc[i]=calc[i-1]*i%mod;
	}
	inv[n]=qpow(calc[n],mod-2);
	for(long long i=n-1;i>=0;i--){
		inv[i]=inv[i+1]*(i+1)%mod;
	}
	return;
}
long long C(int a,int b){//组合数 
	if(b>a)return 0;
	return calc[a]*inv[b]%mod*inv[a-b]%mod;
}
int main(){
	int T=read();
	init(N-5);
	while(T--){
		int n,m;
		n=read(),m=read();//n个桃子,m个猴子 
		if(m==1||n==1){
			puts("1");
			continue;
		}
		long long ans=0;
		for(int i=1;i<=n;i++){//枚举猴王得到的桃子个数 
			long long id=1;
			for(int k=0;n-(k+1)*i>=0;k++){//枚举至少有k只小猴子得到的桃子个数>猴王 
				ans+=id*C(m-1,k)%mod*C(n-(k+1)*i+m-2,m-2)%mod;
				ans%=mod;
				id=-id;
			}
		}
		ans=(ans%mod+mod)%mod;
		printf("%lld\n",ans);
	}
}

相关知识

这道题挺综合的,考了很多组合基本知识

插板法介绍

插板法是解决这么一类问题:不定方程x1+x2+xn=mx_1+x_2……+x_n=m的非负整数解的问题

其中mn为正整数

我们把题目转化成求正整数解,也就是每个数先+1:

x1+1+x2+1+xn+1=m+nx_1+1+x_2+1……+x_n+1=m+n

xi=xi+1x_i=x_i+1

这样取值范围也向上加一

那我们把它看成,m+n个相同小球,分成非空的n堆,这样从左往右n堆分别代表n个x,这就把解转化为划分方式(注意,与第二类斯特林数不同,这个盒子是相同的,没学过的直接忽略这句话)

那么由于现在是非空的,就有了m+n1m+n-1个空隙,插入n1n-1个板子,就是组合数Cm+n1n1C_{m+n-1}^{n-1}

这就是插板法解决不定方程问题。

二项式反演介绍

具体内容见:二项式反演

那么这道题怎么用二项式反演呢?

其实前面的从m1m-1个盒子里面选kk个盒子,就蕴含了二项式反演。

当ik确定的时候(一号盒子有i个球,后m-1个盒子里有k个盒子球数不少于i)

我们令f(k)f(k)表示恰好k个盒子的球数不少于i

g(k)g(k)表示至少k个盒子球数不少于i

那么这里的g函数就和容斥的f函数不一样了。

我就需要钦点k个盒子而不是自由排列组合选出k个盒子。

我钦点了前k个也就是2k+12····{k+1}这几个盒子每个先放进去i个球,然后剩下的插板解决。

这样才能保证g函数表示至少k个盒子而不会算重。

g(k)=Cn(k+1)i+m2m2g(k)=C_{n-(k+1)*i+m-2}^{m-2}

g(k)=j=km1Cm1jf(j)g(k)=\sum_{j=k}^{m-1}{C_{m-1}^{j}f(j)}

为什么要乘组合数呢?

我们来看:

在这里插入图片描述

画个图辅助理解:我们看蓝色点,表示恰好3个。那么绿、黄、红都代表至少两个。

那么在f函数里面,恰好3个的一种方案:蓝色点,在至少两个里面出现了三次。

因为我们回到“至少”函数gg的推导:我们钦点一部分作为一开始各分发i个小球的,剩下球随便给,黄绿红分别代表钦点的方案,剩下的一个点代表随便给球出现的,那么一个蓝色就对应了三种“至少”方案。

因此,可以得到上面的表达式。

那么上面那个表达式代入二项式反演得到容斥的那个式子:

k=0n(k+1)i>=0(1)kCm1kCn(k+1)i+m2m2\sum_{k=0}^{n-(k+1)*i>=0} {(-1)^{k}C_{m-1}^{k}*C_{n-(k+1)*i+m-2}^{m-2}}

小总结

其实发现二项式反演和容斥的最终结果完全一样。

容斥选择至少k个不小于i的盒子直接用组合选。

但是二项式反演要钦点前k个,然后在“至少”和“恰好”之间转化的时候加上第一个组合数。

看似多此一举,实则在以后的难题里运用更广泛。

总结

这道题不失为一道组合好题

它综合运用多种知识,并不难。

这道题用二项式反演其实大材小用了,网上做法很多都是直接容斥。

但是以后遇到更难的题目的时候,想想二项式反演来辅助得到容斥结果也未尝不可。

推荐题目:已经没有什么好害怕的了

附带此题题解:题解

谢谢观看,敬请指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章