HDU5201-The Monkey King
题目
简化题意:1~n个不同盒子,放入m个相同小球,盒子可空,但是1号盒子的球数必须严格最多,问合法方案数。
分析
简化问题
如果没有1号盒子严格最多的限制,就变成了一个插板的问题。
也就是这类不定方程求非负整数解的问题,插板法解决。
容斥
如果枚举猴王(1号盒子)的球数为i
那么问题就变成不定方程并且,加了一个上限。
上限通常会用容斥转化成下限,下限继续插板解决。
枚举至少k个盒子的球数,然后容斥解决问题。
假设现在是要求至少k个盒子的球数不少于1号盒子,那么就先选出k个盒子,也就是
然后向每个选出来的盒子放i个球,剩下的个球随便放入个盒子里面
然后运用插板法,种方法。
子问题得证
现在我们解决了已知1号盒子球数i、至少k个盒子的球数不小于i的方案个数。
然后就用容斥解决。
容斥其实和二项式反演有关,有的时候容斥不太好理解或者好想到,就先想到二项式反演再得到容斥的式子。
这里就先给出容斥的形式,二项式反演后文会讲解。
确定i的时候,令表示至少k个盒子球数不少于i的方案数
那么对于i,答案为:
相关代码
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);
}
}
相关知识
这道题挺综合的,考了很多组合基本知识
插板法介绍
插板法是解决这么一类问题:不定方程的非负整数解的问题
其中mn为正整数
我们把题目转化成求正整数解,也就是每个数先+1:
令
这样取值范围也向上加一
那我们把它看成,m+n个相同小球,分成非空的n堆,这样从左往右n堆分别代表n个x,这就把解转化为划分方式(注意,与第二类斯特林数不同,这个盒子是相同的,没学过的直接忽略这句话)
那么由于现在是非空的,就有了个空隙,插入个板子,就是组合数了
这就是插板法解决不定方程问题。
二项式反演介绍
具体内容见:二项式反演
那么这道题怎么用二项式反演呢?
其实前面的从个盒子里面选个盒子,就蕴含了二项式反演。
当ik确定的时候(一号盒子有i个球,后m-1个盒子里有k个盒子球数不少于i)
我们令表示恰好k个盒子的球数不少于i
表示至少k个盒子球数不少于i
那么这里的g函数就和容斥的f函数不一样了。
我就需要钦点k个盒子而不是*排列组合选出k个盒子。
我钦点了前k个也就是这几个盒子每个先放进去i个球,然后剩下的插板解决。
这样才能保证g函数表示至少k个盒子而不会算重。
为什么要乘组合数呢?
我们来看:
画个图辅助理解:我们看蓝色点,表示恰好3个。那么绿、黄、红都代表至少两个。
那么在f函数里面,恰好3个的一种方案:蓝色点,在至少两个里面出现了三次。
因为我们回到“至少”函数的推导:我们钦点一部分作为一开始各分发i个小球的,剩下球随便给,黄绿红分别代表钦点的方案,剩下的一个点代表随便给球出现的,那么一个蓝色就对应了三种“至少”方案。
因此,可以得到上面的表达式。
那么上面那个表达式代入二项式反演得到容斥的那个式子:
小总结
其实发现二项式反演和容斥的最终结果完全一样。
容斥选择至少k个不小于i的盒子直接用组合选。
但是二项式反演要钦点前k个,然后在“至少”和“恰好”之间转化的时候加上第一个组合数。
看似多此一举,实则在以后的难题里运用更广泛。
总结
这道题不失为一道组合好题
它综合运用多种知识,并不难。
这道题用二项式反演其实大材小用了,网上做法很多都是直接容斥。
但是以后遇到更难的题目的时候,想想二项式反演来辅助得到容斥结果也未尝不可。
推荐题目:已经没有什么好害怕的了
附带此题题解:题解
谢谢观看,敬请指正。
推荐阅读
-
android monkey自动化测试改为java调用monkeyrunner Api
-
详解Python编程中对Monkey Patch猴子补丁开发方式的运用
-
python运行时修改代码的方法——monkey patch
-
C - Monkey and Banana HDU 1069( 动态规划+叠放长方体)
-
android压力测试命令monkey详解
-
详解Python编程中对Monkey Patch猴子补丁开发方式的运用
-
monkey命令解析详解
-
[python]什么是monkey patch
-
APP测试工具monkey的安装和常用命令及日志分析
-
LeetCode-1222. Queens That Can Attack the King