奖品分配
给定 T 组测试用例,每组给出学生人数 N、奖品种类数 M 以及每种奖品的数量,奖品总数满足介于 N 到 N+1 之间,要给每位同学分配一件奖品,求不同分配方案的数目,结果对 \(10^9+7\) 取模后输出。
GESP2分钟阅读

题目关键限制条件是:。这意味着所有奖品刚好分完,或者恰好剩下 0 个 或 1 个 奖品。
如果奖品总数刚好等于学生人数 ,说明每种奖品必须全部发完(第 种奖品正好发掉 个)。
-
这个问题等价于求多重集的全排列。
-
方案数直接用多重组合数计算:
如果有 个奖品,说明分配完 名学生后,会剩下 1 个奖品。
-
转换思路:与其考虑怎么分 个奖品给 个人,不如考虑是哪一种奖品被剩下了。
-
假设剩下的奖品种类是第 种(条件要求 ),那么实际上发出去的第 种奖品数量就变成了 个。
-
此时发出去的奖品总数正好是 个,对应发给学生的方案数为:
-
我们遍历所有可能的 (只要 ),将所有情况的方案数相加即可:
在情况二中,如果对每个 都单独计算一次多重组合数,时间复杂度会极高。我们可以对式子进行化简:
注意到:
将这个形式代入求和公式中,提公因式得到:
因为已知 ,代入上式化简为:
也就是说:
- 若 :答案为
- 若 :答案直接为
- 预处理阶乘与逆元:
- 题目中 ,数据范围较小。
- 预处理出 到 的阶乘
fact[i],以及对应的模 意义下的乘法逆元invFact[i](可用费马小定理 + 快速幂求解)。
- 处理每组询问:
- 计算当前班级所有 的和 。
- 分母部分 。
- 若 :输出 。
- 若 :输出 。
单次询问时间复杂度为 ,整体复杂度为 ,可在 的限制内高效通过。
#include <iostream>#include <vector>
using namespace std;
static const int MOD = 1000000007;static const int MAXN = 2005;
// 阶乘与逆元数组long long fact[MAXN];long long invFact[MAXN];
// 快速幂计算 (a^b % MOD)long long power(long long base, long long exp) { long long res = 1; base %= MOD; while (exp > 0) { if (exp % 2 == 1) res = (res * base) % MOD; base = (base * base) % MOD; exp /= 2; } return res;}
// 费马小定理求乘法逆元long long modInverse(long long n) { return power(n, MOD - 2);}
// 预处理阶乘和阶乘逆元void precompute() { fact[0] = 1; invFact[0] = 1; for (int i = 1; i < MAXN; i++) { fact[i] = (fact[i - 1] * i) % MOD; invFact[i] = modInverse(fact[i]); }}
void solve() { int n, m; if (!(cin >> n >> m)) return;
long long sum_a = 0; long long denominator_inv = 1; // 用于累乘 1 / (a_i!)
for (int i = 0; i < m; i++) { long long a; cin >> a; sum_a += a; denominator_inv = (denominator_inv * invFact[a]) % MOD; }
// 根据总数分类讨论并使用化简公式 if (sum_a == n) { long long ans = (fact[n] * denominator_inv) % MOD; cout << ans << "\n"; } else if (sum_a == n + 1) { long long ans = (fact[n + 1] * denominator_inv) % MOD; cout << ans << "\n"; }}
int main() { // 优化标准输入输出流加速 ios_base::sync_with_stdio(false); cin.tie(NULL);
precompute();
int t; if (cin >> t) { while (t--) { solve(); } }
return 0;}