跳过并跳转到主要内容
BigO

奖品分配

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

GESP2分钟阅读

题目关键限制条件是:N≤∑ai≤N+1N \le \sum a_i \le N + 1。这意味着所有奖品刚好分完,或者恰好剩下 0 个 或 1 个 奖品。

如果奖品总数刚好等于学生人数 NN,说明每种奖品必须全部发完(第 ii 种奖品正好发掉 aia_i 个)。

  • 这个问题等价于求多重集的全排列。

  • 方案数直接用多重组合数计算:

    N!a0!⋅a1!⋅⋯⋅aM−1!\frac{N!}{a_0! \cdot a_1! \cdot \dots \cdot a_{M-1}!}

如果有 N+1N+1 个奖品,说明分配完 NN 名学生后,会剩下 1 个奖品。

  • 转换思路:与其考虑怎么分 NN 个奖品给 NN 个人,不如考虑是哪一种奖品被剩下了。

  • 假设剩下的奖品种类是第 kk 种(条件要求 ak≥1a_k \ge 1),那么实际上发出去的第 kk 种奖品数量就变成了 ak−1a_k - 1 个。

  • 此时发出去的奖品总数正好是 NN 个,对应发给学生的方案数为:

    N!a0!⋅⋯⋅(ak−1)!⋅⋯⋅aM−1!\frac{N!}{a_0! \cdot \dots \cdot (a_k - 1)! \cdot \dots \cdot a_{M-1}!}

  • 我们遍历所有可能的 kk(只要 ak>0a_k > 0),将所有情况的方案数相加即可:

    总方案数=∑k=0,ak>0M−1N!a0!⋯(ak−1)!⋯aM−1!\text{总方案数} = \sum_{k=0, a_k > 0}^{M-1} \frac{N!}{a_0! \cdots (a_k - 1)! \cdots a_{M-1}!}

在情况二中,如果对每个 kk 都单独计算一次多重组合数,时间复杂度会极高。我们可以对式子进行化简:

注意到:

N!a0!⋯(ak−1)!⋯aM−1!=N!∏ai!×ak\frac{N!}{a_0! \cdots (a_k - 1)! \cdots a_{M-1}!} = \frac{N!}{\prod a_i!} \times a_k

将这个形式代入求和公式中,提公因式得到:

总方案数=N!∏i=0M−1ai!×∑k=0M−1ak\text{总方案数} = \frac{N!}{\prod_{i=0}^{M-1} a_i!} \times \sum_{k=0}^{M-1} a_k

因为已知 ∑ai=N+1\sum a_i = N + 1,代入上式化简为:

总方案数=N!∏i=0M−1ai!×(N+1)=(N+1)!∏i=0M−1ai!\text{总方案数} = \frac{N!}{\prod_{i=0}^{M-1} a_i!} \times (N + 1) = \frac{(N + 1)!}{\prod_{i=0}^{M-1} a_i!}

也就是说:

  • 若 ∑ai=N\sum a_i = N:答案为 N!∏ai!\frac{N!}{\prod a_i!}
  • 若 ∑ai=N+1\sum a_i = N + 1:答案直接为 (N+1)!∏ai!\frac{(N + 1)!}{\prod a_i!}

  1. 预处理阶乘与逆元:
    • 题目中 N≤1000N \le 1000,数据范围较小。
    • 预处理出 00 到 N+1N+1 的阶乘 fact[i],以及对应的模 109+710^9 + 7 意义下的乘法逆元 invFact[i](可用费马小定理 + 快速幂求解)。
  2. 处理每组询问:
    • 计算当前班级所有 aia_i 的和 S=∑aiS = \sum a_i。
    • 分母部分 D=∏i=0M−1(ai! mod (109+7))D = \prod_{i=0}^{M-1} (a_i! \bmod (10^9+7))。
    • 若 S=NS = N:输出 fact[N]×D−1 mod (109+7)\text{fact}[N] \times D^{-1} \bmod (10^9+7)。
    • 若 S=N+1S = N + 1:输出 fact[N+1]×D−1 mod (109+7)\text{fact}[N+1] \times D^{-1} \bmod (10^9+7)。

单次询问时间复杂度为 O(M)O(M),整体复杂度为 O(max⁡(N)+T⋅M)O(\max(N) + T \cdot M),可在 1.00s1.00\text{s} 的限制内高效通过。

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

© 2026 三七开 · 一方通行,只管向前。

旅途由 Astro 驱动 · 主题 Chirping Astro