【算法漫笔006】盒子与球
2026-08-28 20:56:27
发布于:重庆
咕咕咕
这期注释比正文还长。
【算法漫笔006】浅谈盒子与球
这篇写了很久,主要原因是我比较懒这篇太长太难写了。
注释最多的一集。写的最长的一集。
盒子与球
盒子与球主要是一种排列组合,其问题形式为:将 个球放入 个盒子中,根据球是否相同[1]、盒子是否相同、盒子中至少一个球/至多一个球/无限制的放球,组合成不同的计数问题。我们来一个个解析这些问题。
为了方便,我们将每个问题编码,第一位表示球相同()/不同(),第二位表示盒子相同()/不同()。第三位表示盒子中无限制的放球()/至少一个球()/至多一个球()。
如果你对排列组合不太熟悉,以下的内容请配合注释理解。
00A
即球不同,盒子不同,无限制。
此时每个球都是独立的,可以放入任何一个盒子当中。根据乘法原理[2],此时总方案数为:
00B
即球不同,盒子不同,每个盒子至少放一个球。
此时我们可以先将 个球分成 个非空的无序集合,显然方案数为 [3]。此时我们已经将 个不同的球分成了 个非空的无序集合,但题目要求盒子是不同的,所以我们还需要把这 个集合分配给 个不同的盒子。这相当于对 个集合进行全排列[4],方案数为 。再次根据乘法原理得到:
特别的,当 时,我们无法保证每个盒子至少放一个球,故方案数为 。
00C
即球不同,盒子不同,每个盒子至多放一个球。
因为每个盒子最多只能放一个球,所以先从 个盒子中选出 个来放球,方案数为[5]。然后将 个不同的球与选出的 个不同盒子一一对应(即全排列[4:1]),方案数为 。根据乘法原理[2:1],总方案数为:
特别的,当 时,我们无法保证每个盒子至多放一个球,故方案数为 。
01A
即球不同,盒子相同,无限制。
此时盒子不可区分,但球仍可区分。我们需要将 个不同的球分成不超过 个非空[6]无序集合。
此时我们可以枚举非空盒子的数量 ,其中 从 到 ,对于每个 ,方案数为第二类斯特林数 [3:1]。根据加法原理[7],总方案数为:
特别的,当 时,方案数为 (全空盒);当 时,若 则方案数为 ,否则为 。
01B
即球不同,盒子相同,每个盒子至少放一个球。
根据 [3:2]的定义,答案为:
特别的,当 时,我们无法保证每个盒子至少放一个球,故方案数为 。
01C
即球不同,盒子相同,每个盒子至多放一个球。
因为盒子相同,且每个盒子最多放一个球,所以实际上只有两种可能,即盒子能够放下和无法全部放下,即:
10A
即球相同,盒子不同,无限制。
此时球完全相同,但盒子可区分。也就是将 个无区别的球放入 个有编号的盒子,允许空盒。使用隔板法[8]解决,答案为:
特别的,当 时,方案数为 (全空盒);当 时,若 则方案数为 ,否则为 。
10B
即球相同,盒子不同,每个盒子至少放一个球。
依然使用隔板法[8:1]。先想象:给每个盒子放 个球,此时这些盒子都欠我 个球。剩下 个球,问题转化为将 个相同球放入 个不同盒子(允许空盒),即 10A 的情形。因此方案数为:
特别的,当 时,我们无法保证每个盒子至少放一个球,故方案数为 。
10C
即球相同,盒子不同,每个盒子至多放一个球。
因为球相同且每个盒子最多一个,所以实际上就是从 个不同的盒子中选出 个来放球(每个盒子恰好一个球)。符合组合数[5:1]的定义,答案为:
特别的,当 时,我们无法保证每个盒子至多放一个球,故方案数为 。
11A
即球相同,盒子相同,无限制。
此时球完全相同,盒子也完全相同。问题转化为:将整数 拆分为至多 个正整数之和[9]。我们定义 dp 表示 的整数拆分数[10],则答案为:
特别的,当 时,方案数为 (全空盒);当 时,若 则方案数为 ,否则为 。
11B
即球相同,盒子相同,每个盒子至少放一个球。
依然先让每个盒子欠债一颗球,剩下 个球。问题转化为将 个相同球放入 个相同盒子(允许空盒),即 11A 的情形。因此方案数为:
特别的,当 时,我们无法保证每个盒子至少放一个球,故方案数为 。
11C
即球相同,盒子相同,每个盒子至多放一个球。
因为球相同且盒子相同,每个盒子最多一个球,所以实际只有两种可能,若盒子够用为 ,不够用为 。
题目
总的来看还是有一定的套路的,我自己认为我写的还比较好懂。
诶诶怎么是黑题?诶诶怎么要用 !前面的区域以后再来探索吧。
还是弱化版更适合我们!我们来写一下这个青题吧!
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MOD=998244353;//83517427
const int N=1e3+5;
int n,m,S[N][N],dp[N][N];
int slow_pow(int x,int y) {
int res=1;
while (y) {
if (y&1) res=res*x% MOD;
x=x*x%MOD;
y>>=1;
}
return res;
}
void init() {
S[0][0]=1;
for (int i=1;i<=N-5;i++) {
S[i][0]=0;
S[i][i]=1;
for (int j=1;j<i;j++) {
S[i][j]=(S[i-1][j-1]+j*S[i-1][j])%MOD;
}
}
}
void init_dp() {
memset(dp,0,sizeof dp);
dp[0][0]=1;
for (int i=0;i<=N-5;i++) {
for (int j=1;j<=N-5;j++) {
if (i>=j) dp[i][j] =(dp[i][j-1]+dp[i-j][j])%MOD;
else dp[i][j]=dp[i][j-1];
}
}
}
int jc(int n) {
int res=1;
for (int i=1;i<=n;i++) {
res=(res*i)%MOD;
}
return res;
}
int C(int n,int m) {
if(m<0||m>n) return 0;
return jc(n)*slow_pow(jc(m),MOD-2)%MOD*slow_pow(jc(n-m),MOD-2)%MOD;
}
signed main() {
init();
init_dp();
cin >> n >> m;
cout << slow_pow(m,n)%MOD << endl;
if (n>m) cout << "0\n";
else {
int ans=1;
for (int i=0;i<n;i++) ans=ans*(m-i)%MOD;
cout << ans << endl;
}
if (n<m) cout << "0\n";
else cout << (jc(m)*S[n][m])%MOD << endl;
int ans=0;
for (int i=1;i<=m;i++) {
ans=(ans+S[n][i]%MOD);
}
cout << ans%MOD << endl;
if (n<=m) cout << "1\n";
else cout << "0\n";
cout << S[n][m] << endl;
cout << C(n+m-1,m-1) << endl;
if (n<=m) cout << C(m,n) << endl;
else cout << "0\n";
cout << C(n-1,m-1) << endl;
cout << dp[n][m] << endl;
if (n<=m) cout << "1\n";
else cout << "0\n";
if (n<m) cout << "0\n";
else cout << dp[n-m][m] << endl;
return 0;
}
还不算很难!
题外话
好想写 DP 但是不知道怎么写,DP 这个东西太多变了,没有完整的流程,所以还是单开一个专题比较好,算法漫笔的第六篇还是写盒子与球吧。总之就是我还是写数论了
注释&致谢
感谢 StackEdit软件,欢迎大家去使用这个免费的 Markdown 编辑软件。它不仅可以在线使用,还可以下载使用。
相同:在盒子与球模型里,相同和不同指的是物体本身是否可区分,例如,在球不同的情况下,将两个盒子中分别放入一个球,是一种方案,而将两个盒子中的球调换则是另一种方案。而在球相同的情况下,我们视以上两种方案为一种。 ↩︎
乘法原理:即如果完成一件事需要 个步骤,第 个步骤有 种不同的方法,且这些步骤互不影响,那么完成这件事总共有 种方法。 ↩︎ ↩︎ ↩︎ ↩︎
: 为第二类斯特林数, 表示将 个不同元素划分为 个非空无序集合的方案数,满足递推:其中:公式的推导也很简单:考虑第 个元素,其有两种情况:
自己单独作为一个非空集合:那么剩余的 个元素就要划分成 个非空集合,根据定义方案数为:加入已有集合:先将前 个元素划分成 个非空集合,根据定义方案数为 。然后第 个元素可以放入这 个集合中的任意一个,有 种选择。根据乘法原理[2:2]得到:最后根据加法原理[7:1]得到递推公式:
边界情况也很好理解,(全部元素只能放在一个集合里),(每个元素各自成一组)。 ↩︎ ↩︎ ↩︎全排列:即把 个不同的元素按任意顺序排成一列,方案数为 。其定义是:从 个不同元素中取出 个,按顺序排列,共有 种方法。 ↩︎ ↩︎
组合数: 表示组合数,即从 个不同元素中选出 个的方案数,计算公式为 。公式的推导为:先从 个元素中选 个按顺序排列(排列数[11] ),但组合不考虑顺序,而 个元素的内部顺序有 种,因此需要除以 ,即: ↩︎ ↩︎
非空:因为空盒在盒子相同时没有区别。 ↩︎
隔板法:即将问题转化为在多个球中插入隔板,计算隔板的排列。此时由于两个隔板不可能重合,所以可以保证每个集合非空。 ↩︎ ↩︎
问题转化:因为盒子相同,空盒没有区别,所以只需关注非空盒子的个数。 ↩︎
整数拆分数:这里定义的整数拆分数 ,表示将正整数 拆分为至多 个正整数之和的方案数。可以依靠递推公式 。很好理解的递推公式此处不再解释。其中 ,当 时 。 ↩︎
排列数:即从 个不同元素中取出 个,按顺序排成一列,方案数为 。它与组合数的区别在于:排列考虑顺序,组合不考虑顺序。第一个位置有 种选择,第二个位置有 种选择,……,第 个位置有 种选择,根据乘法原理[2:3],总方案数为 。 ↩︎
全部评论 2
ACGO 这个沙子Markdown为什么我的注释不能换行。。。
2天前 来自 重庆
0咕咕咕
2天前 来自 重庆
0













有帮助,赞一个