当小球遇上盒子——组合计数入门
当小球遇上盒子
一套排列组合题:盒子是否相同,球是否相同,能否有空盒子共8个状态
PS:代码上
PS:
盒不同,球相同,不为空
现在有
既然球相同,不妨排成一列,这样我们就可以把盒子看做分隔符。
这样问题就变成了:选出
一共有
int r,inv[15],jc[15],n;
int main(){
cin>>n>>r;
jc[1]=jc[0]=1;for(int i=2;i<=12;i++)jc[i]=jc[i-1]*i;
cout<<jc[n-1]/jc[r-1]/jc[n-r]<<"\n";
}
盒不同,球相同,能为空
这时候可能出现分隔符可以放一起的情况,怎么办呢?如何处理这种情况?
如果我们能转化为第一种情况,就好了,但如何转化呢?
不妨将分隔符也看做球,这样就可以转化了——Why?因为这时候等价于从
#define int long long
using namespace std;
int r,inv[25],jc[25],n;
int C(int n,int m){
return jc[n]/jc[m]/jc[n-m];
}
signed main(){
cin>>n>>r;
jc[1]=jc[0]=1;for(int i=2;i<=22;i++)jc[i]=jc[i-1]*i;
cout<<C(n+r-1,r-1)<<"\n";
}
盒相同,球不同,不能空
此问题等价于第二类斯特林数,考虑进行DP求解:
设
初始化:
目标:
则考虑转移:
- 不新开盒子:
:从 个盒子里找一个放进去 - 新开盒子:
转移:
#include<iostream>
#include<cstdio>
using namespace std;
#define int long long
int f[105][105];
signed main(){
int n,m;cin>>n>>m;
f[1][1]=f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j]=f[i-1][j]*j+f[i-1][j-1];
}
}
cout<<f[n][m]<<"\n";
}
盒相同,球不同,能为空
这个问题可以由于盒子是一样的,那么能为空的可以直接不管,类比上一个,可以得到其答案为:
#define int long long
int f[105][105];
signed main(){
int n,m;cin>>n>>m;
f[1][1]=f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j]=f[i-1][j]*j+f[i-1][j-1];
}
}
int ans=0;
for(int i=0;i<=m;i++)ans+=f[n][i];
cout<<ans<<"\n";
}
盒不同,球不同,不能空
这个题可以换一个角度,与盒相同,球不同,不能空的情况相比,只是多了盒不同这一个限制。
那么大可以将这个盒子进行编号,然后就对这种情况进行计数即可。一共有
#include<iostream>
#include<cstdio>
using namespace std;
#define int long long
int f[105][105];
signed main(){
int n,m;cin>>n>>m;
f[1][1]=f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j]=f[i-1][j]*j+f[i-1][j-1];
}
}
int ans=1;for(int i=1;i<=m;i++)ans*=i;
cout<<f[n][m]*ans<<"\n";
}
盒不同,球不同,能为空
这玩意更屑了。
容易发现这玩意其实并不具备任何对方法进行限制的条件,也不需要去重。所以答案就是
#include<iostream>
#include<cstdio>
using namespace std;
#define int long long
signed main(){
int n,m;cin>>n>>m;
int ans=1;for(int i=1;i<=n;i++)ans*=m;
cout<<ans<<"\n";
}
盒相同,球相同,能为空
这玩意还是考虑DP求解。
设
初始:
目标:
类比
,
考虑对方案数分成两类:没有一个盒子为空与有盒子为空的方案数
没有盒子为空,可以考虑将所有盒子里都放一个球,方案数是
有盒子为空,直接加上
,这时候新开的盒子是空的,满足题设
,此时只能新开一个为空的(鸽巢原理)——为了去重(想一想,为什么/doge)
进行转移即可。
#include<iostream>
#include<cstdio>
using namespace std;
#define int long long
int f[105][105];
signed main(){
int n,m;cin>>n>>m;
for(int i=1;i<=m;i++)f[0][i]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i>=j)f[i][j]=(f[i-j][j]+f[i][j-1]);
else f[i][j]=f[i][j-1];
}
}
cout<<f[n][m]<<"\n";
}
盒相同,球相同,不为空
这时候类比之前的方法,给每个盒子预先放一个球,由于球和盒子是一样的,方案唯一
这样就变成了上一个问题,答案为
#include<iostream>
#include<cstdio>
using namespace std;
#define int long long
int f[1005][1005];
signed main(){
int n,m;cin>>n>>m;
for(int i=1;i<=m;i++)f[0][i]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i>=j)f[i][j]=(f[i-j][j]+f[i][j-1])%12345;
else f[i][j]=f[i][j-1];
}
}
cout<<f[n-m][m]%12345<<"\n";
}
延伸模型
模型1
给定
个相同的盒子, 个相同的球,每个盒子最少放 个,求方案数。
设
初始化:
注意三个限制条件是有优先级的
转移:
目标:
正确性:这个做法保证了在每一个方案里面盒子的排列是单调不增的。
方案数很大。
可以使用记忆化搜索完成
模型2
个不同的盒子, 个相同的球,每个盒子至少放 个球。求方案数
解:设
可以看作每个盒子预先放
模型3
有解:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!