当小球遇上盒子——组合计数入门

当小球遇上盒子

一套排列组合题:盒子是否相同,球是否相同,能否有空盒子共8个状态

PS:代码上f,g的两个维度是反着写的

PS:(nm)=n!m!(nm)!=Cnm

盒不同,球相同,不为空

现在有r个互不相同的盒子和n个相同的球,要将这n个球放入r个盒子中,且不允许有空盒子,问有多少种放法

既然球相同,不妨排成一列,这样我们就可以把盒子看做分隔符

这样问题就变成了:选出r1个分隔符的选法。

一共有n1个分隔符,所以答案为:

(n1r1)

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?因为这时候等价于从n+r1个物品里拿出r1作为分隔符。答案即为:

(n+r1r1)

#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求解:

f[i,j]表示i个盒子j个球不能空的方案数:

初始化:f[0,0]=f[1,1]=1

目标: f[r,n]

则考虑转移:

  1. 不新开盒子:if[i,j1]:从i个盒子里找一个放进去
  2. 新开盒子:f[i1,j1]

转移:f[i,j]=if[i,j1]+f[i1,j1]


#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";
}

盒相同,球不同,能为空

这个问题可以由于盒子是一样的,那么能为空的可以直接不管,类比上一个,可以得到其答案为:

k=0if[k,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";
}

盒不同,球不同,不能空

这个题可以换一个角度,与盒相同,球不同,不能空的情况相比,只是多了盒不同这一个限制。

那么大可以将这个盒子进行编号,然后就对这种情况进行计数即可。一共有r!种编号方式,所以答案为:r!f[r,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";
}

盒不同,球不同,能为空

这玩意更屑了。

容易发现这玩意其实并不具备任何对方法进行限制的条件,也不需要去重。所以答案就是rn

#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求解。

g[i,j]表示i个盒子j个球的答案。

初始:g[i,0]=1

目标:g[r,n]

类比f的方法,还是考虑用新不新开来搞

  1. ij

考虑对方案数分成两类:没有一个盒子为空与有盒子为空的方案数

没有盒子为空,可以考虑将所有盒子里都放一个球,方案数是g[i,ji]

有盒子为空,直接加上g[i1,j],这时候新开的盒子是空的,满足题设

g[i,j]=g[i,ji]+g[i1,j]

  1. i>j,此时只能新开一个为空的(鸽巢原理)——为了去重(想一想,为什么/doge)

g[i,j]=g[i1,j]

进行转移即可。


#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";
}

盒相同,球相同,不为空

这时候类比之前的方法,给每个盒子预先放一个球,由于球和盒子是一样的,方案唯一

这样就变成了上一个问题,答案为g[r,nr]

#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

给定n个相同的盒子,m个相同的球,每个盒子最少放t个,求方案数。

f[i,j,k]表示i个盒子,j个球,每个盒子最少k个的方案数。

初始化:

  1. ik>j,f[i,j,k]=0
  2. kj,f[1,j,k]=1
  3. f[0,j,k]=0

注意三个限制条件是有优先级的

转移:

f[i,j,k]=p=kjf[i1,jp,p]

目标:

f[n,m,t]

正确性:这个做法保证了在每一个方案里面盒子的排列是单调不增的。

方案数很大。

可以使用记忆化搜索完成

模型2

n个不同的盒子,m个相同的球,每个盒子至少放a1,a2an个球。求方案数

解:设S=i=1n(ai1),则方案数为:(mS1n1)

可以看作每个盒子预先放ai1个,问题转化为求盒子不为空的方案数。

模型3

n个不同的盒子,m个相同的球,每个盒子最多一个球,非空盒子不能相邻。

有解:2m1n

posted @   spdarkle  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示