盒与球问题
典题合集
编号 | 球是否相同 | 盒子是否相同 | 是否允许空盒 | 思路 | 扩展 |
---|---|---|---|---|---|
1 | 相同 | 不同 | 否 | 组合数 | |
2 | 相同 | 不同 | 是 | 组合数 | |
3 | 相同 | 相同 | 是 | dp | |
4 | 相同 | 相同 | 否 | dp | 在3思路上扩展 |
5 | 不同 | 相同 | 否 | dp | |
6 | 不同 | 相同 | 是 | dp | 在5思路上扩展 |
7 | 不同 | 不同 | 否 | dp | 在5思路上扩展 |
8 | 不同 | 不同 | 是 | 组合数 |
规定n:球数,m:盒子数
1.球相同,盒子不同,无空盒
挡板法,相当于将n个球分成m组,相当于在n−1位置中插入m−1块板子。
[公式]
\(C_{n-1}^{m-1}\)
2.球相同,盒子不同,有空盒
我们可以假设每个盒子里都已经放了一个球,这个时候实际上就有 N+M个球了,我们再次使用隔板法。对于每一种方案,我们就可以想象成每个空隙右边的第一个球是提前放好在盒子里的(即盒子内只有一个球),如果将这个球省略,正好就是盒子允许为空的一种方案。
[公式]
\(C_{N+M-1}^{M-1}\)
3.球相同,盒子相同,有空盒
假设f[n][m]为n个球放到m个盒子里的方案数。
如果n<m,此时m个盒子必然装不满,可得f[n][m]=f[n][n]。
如果n>=m,此时可以选择将盒子放满或者盒子不放满。
(1)如果没放满,那就减掉一个盒子,此时为f[i][j−1](即由当前第j个盒子为空盒子)。
(2)如果放满了,那就在每个盒子上里放一个球。现在就是f[i-j][j]。(即i-j个球放j个盒子的情况)。
[公式]
f[i][j]=f[i][j−1]+f[i−j][j]
如果没有球或者只有一个盒子,此时方案数为1,即f[0][j]=f[i][1]=1
[时间复杂度]
O(nm) n:球数,m:盒子数
const int N=1010;
ll f[N][N];//f[n][m]为n个球放到m个盒子里的方案数
void init(int n,int m){//n:球数,m:盒子数
for(int i=0;i<=n;i++){
for(int j=1;j<=m;j++){
if(j==1||i==0) f[i][j]=1;
else if(i<j) f[i][j]=f[i][i];
else if(i>=j) f[i][j]=f[i-j][j]+f[i][j-1];
}
}
}
ll get_schemes(int n,int m){//n:球数,m:盒子数,允许n=0,但m!=0
init(n,m);
return f[n][m];
}
4.球相同,盒子相同,无空盒
这种情况可以由(3.球相同,盒子相同,有空盒)情况推到出来
当球的数量小于盒子的数量:有0种可能
当球的数量等于盒子的数量:有1种可能
当球的数量大于盒子的数量:先把每个盒子放上一个球,然后问题就是第一种情况:f[n-m][m]。
const int N=1010;
ll f[N][N];//f[n][m]为n个球放到m个盒子里的方案数
void init(int n,int m){//n:球数,m:盒子数
for(int i=0;i<=n;i++){
for(int j=1;j<=m;j++){
if(j==1||i==0) f[i][j]=1;
else if(i<j) f[i][j]=f[i][j];
else if(i>=j) f[i][j]=f[i-1][j]+f[i][j-1];
}
}
}
ll get_schemes(int n,int m){
init(n,m);
if(n<m) return 0;
else if(n==m) return 1;
else return f[n-m][m];
}
5.球不同,盒子相同,无空盒
用f[i][j]记录i个球放到j个盒子的情况,首先可以对f[i][j]进行初始化的操作。
(1)就是i个球放到i个盒子的可能性为1
(2)f[i][j]可以由两种情况推导得到
- 第i个球放到新开一个盒子,假设j就是那个新开的盒子,那么f[i][j]就是i-1个球放到j-1个盒子的情况
- 第i个球放到之前的盒子,那么结果可以由i-1个球放到j个盒子的情况得到,并且第i个球有j种选择
- 公式:f[i][j]=f[i-1][j-1]+j*f[i-1][j];
[第二类斯特林数]
定义:将n个物品分成k个非空集合的方案数,记作\(\left.\left\{\begin{matrix}n\\k\end{matrix}\right.\right\}\),称为斯特林子集数.
公式:\(\left.\left\{\begin{array}{c}n\\k\end{array}\right.\right\}=k\left\{\begin{array}{c}n-1\\k\end{array}\right\}+\left\{\begin{array}{c}n-1\\k-1\end{array}\right\}\)
意义:放入最后一个物品时,若新成立一个集合,方案数为\(\left.\left\{\begin{array}{l}n-1\\k-1\end{array}\right.\right\}\),若加入原来的集合,方案数为\(\left.k\left\{\begin{gathered}n-1\\k\end{gathered}\right.\right\}\)。
const int N=1010;
ll f[N][N];//f[n][m]为n个球放到m个盒子里的方案数
void init(int n,int m){//n:球数,m:盒子数
f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
f[i][j]=f[i-1][j-1]+j*f[i-1][j];
}
}
}
ll get_schemes(int n,int m){
init(n,m);
return f[n][m];
}
6.球不同,盒子相同,有空盒
对(5.球不同,盒子相同,无空盒)扩展,只需要枚举非空盒子的对应的方案数量即可。
const int N=1010;
ll f[N][N];//f[n][m]为n个球放到m个盒子里的方案数
void init(int n,int m){//n:球数,m:盒子数
f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
f[i][j]=f[i-1][j-1]+j*f[i-1][j];
}
}
}
ll get_schemes(int n,int m){
init(n,m);
ll res=0;
for(int i=1;i<=m;i++){
res+=f[n][i];
}
return res;
}
7.球不同,盒子不同,无空盒
对(5.球不同,盒子相同,无空盒)扩展,我在第5种的情况下,考虑盒子不同的问题。
盒子不同的情况,球它的一个排列。
const int N=20;
ll f[N][N];//f[n][m]为n个球放到m个盒子里的方案数
ll b[N];
void init(int n,int m){//n:球数,m:盒子数
f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
f[i][j]=f[i-1][j-1]+j*f[i-1][j];
}
}
b[0]=1;
for(int i=1;i<=m;i++){//求m!
b[i]=b[i-1]*i;
}
}
ll get_schemes(int n,int m){
init(n,m);
return b[m]*f[n][m];//可能会爆long long
}
8.球不同,盒子不同,有空盒
每个球都有对应盒子的选择,即盒子^(球)。
ll qmi(ll a,ll b){
ll res=0;
while(b){
if(b&1) res=res*a;
a=a*a;
b>>=1;
}
return res;
}
ll get_schemes(int n,int m){
return qmi(m,n);//可能会爆long long
}