奖品总数 S 只可能是 N 或 N+1,方案数就是多重集合排列 S! / ∏a_i!。
P10112 [GESP202312 八级] 奖品分配
GESP8 冲刺课堂页:先建模,再压缩状态,最后读代码。
一、先回答问题
按学生逐个分奖品会产生大量重复状态,也很难处理同类奖品不可区分。
二、核心观察
把 N 个学生加上可能的一个“垃圾桶”看作 S 个位置,把同类奖品排列进去。
为什么不丢答案:每个合法分配唯一对应一个长度为 S 的多重集合排列,垃圾桶只吸收多出来的一个奖品。
S
阶乘
逆元
三、互动演示
步骤1
关键状态 A-
关键状态 B-
答案/目标-
复杂度看表
课堂动作手推
点击下一步开始。
四、自己选答案
S=N+1 时多出的奖品怎么处理?
先判断,再点按钮。
为什么要除以 a_i!?
先判断,再点按钮。
五、手推结果
| 量 | 含义 |
|---|---|
| S! | 所有奖品排到位置 |
| a_i! | 同类奖品内部重复 |
| 逆元 | 模意义下做除法 |
课堂要求:每题先说清对象、关系、保留的信息、压缩掉的信息,再写代码。
六、C++14 参考代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXS = 2005; // 虽然 N<=1000, 但为了保险开大一点
ll fac[MAXS], inv[MAXS];
// 快速幂求逆元
ll power(ll a, ll b) {
ll res = 1;
a %= MOD;
while (b > 0) {
if (b % 2 == 1) res = (res * a) % MOD;
a = (a * a) % MOD;
b /= 2;
}
return res;
}
ll get_inv(ll n) {
return power(n, MOD - 2);
}
void precompute() {
fac[0] = 1;
for (int i = 1; i < MAXS; i++) {
fac[i] = (fac[i - 1] * i) % MOD;
}
}
void solve() {
int n, m;
if (!(cin >> n >> m)) return;
ll s = 0;
vector<int> a(m);
for (int i = 0; i < m; i++) {
cin >> a[i];
s += a[i];
}
ll ans = fac[s];
for (int i = 0; i < m; i++) {
ans = (ans * get_inv(fac[a[i]])) % MOD;
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
precompute();
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}