找回密码
 立即注册
查看: 4|回复: 0

2023年GESP八级考题解析:奖品分配的组合数学解法

[复制链接]
  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 打卡月天数:4
  • 打卡总奖励:29
  • 最近打卡:2025-07-12 15:59:50

7

主题

2

回帖

2316

积分

VIP

积分
2316
发表于 5 天前 | 显示全部楼层 |阅读模式
截图未命名.jpg
一、问题背景
题目要求将N个奖品分配给M个团队,每个团队有特定的奖品需求a。需要计算两种情况的分配方案数:
  • 刚好分配完所有奖品
  • 恰好剩余1个奖品未分配

二、算法核心思想
  • 阶乘预处理:预先计算阶乘和逆阶乘数组
  • 快速幂优化:使用快速幂计算模逆元
  • 多项式系数:利用组合数公式计算分配方案
  • 模运算处理:确保大数运算在模数范围内

三、完整代码解析(带详细注释)
  1. #include <iostream>
  2. #include <vector>
  3. using namespACe std;

  4. const int MOD = 1e9 + 7;  // 标准模数

  5. // 预计算阶乘和逆阶乘数组
  6. vector<long long> fact, inv_fact;

  7. // 快速幂计算(用于求模逆元)
  8. long long pow_mod(long long x, long long n) {
  9.     long long res = 1;
  10.     while (n > 0) {
  11.         if (n % 2 == 1) res = (res * x) % MOD;
  12.         x = (x * x) % MOD;
  13.         n /= 2;
  14.     }
  15.     return res;
  16. }

  17. // 初始化阶乘表(O(n)预处理)
  18. void init_fact(int max_n) {
  19.     fact.resize(max_n + 1);
  20.     inv_fact.resize(max_n + 1);
  21.     fact[0] = 1;
  22.     for (int i = 1; i <= max_n; ++i) {
  23.         fact[i] = (fact[i-1] * i) % MOD;  // 计算i! mod MOD
  24.     }
  25.     // 费马小定理求逆元
  26.     inv_fact[max_n] = pow_mod(fact[max_n], MOD-2);
  27.     // 递推计算逆阶乘
  28.     for (int i = max_n-1; i >= 0; --i) {
  29.         inv_fact[i] = (inv_fact[i+1] * (i+1)) % MOD;
  30.     }
  31. }

  32. // 计算组合数C(n,k) mod MOD
  33. long long comb(int n, int k) {
  34.     if (k < 0 || k > n) return 0;
  35.     return fact[n] * inv_fact[k] % MOD * inv_fact[n-k] % MOD;
  36. }

  37. int main() {
  38.     ios::sync_with_stdio(false);  // 加速IO
  39.     cin.tie(nullptr);
  40.    
  41.     int T;  // 测试用例数
  42.     cin >> T;
  43.    
  44.     // 预处理阶乘表(**支持1e5+5)
  45.     init_fact(1e5 + 5);
  46.    
  47.     while (T--) {
  48.         int N, M;  // 奖品总数和团队数
  49.         cin >> N >> M;
  50.         vector<int> a(M);  // 各团队需求
  51.         int sum = 0;
  52.         for (int i = 0; i < M; ++i) {
  53.             cin >> a[i];
  54.             sum += a[i];
  55.         }
  56.         
  57.         long long ans = 1;
  58.         if (sum == N) {
  59.             // 情况1:刚好分配完
  60.             ans = fact[N];  // N! / (a1!a2!...am!)
  61.             for (int i = 0; i < M; ++i) {
  62.                 ans = ans * inv_fact[a[i]] % MOD;
  63.             }
  64.         } else {
  65.             // 情况2:剩余1个奖品
  66.             ans = 0;
  67.             for (int i = 0; i < M; ++i) {
  68.                 if (a[i] > 0) {
  69.                     // 假设剩余的是第i种奖品
  70.                     long long temp = fact[N];
  71.                     for (int j = 0; j < M; ++j) {
  72.                         int cnt = (j == i) ? (a[j] - 1) : a[j];
  73.                         temp = temp * inv_fact[cnt] % MOD;
  74.                     }
  75.                     ans = (ans + temp) % MOD;
  76.                 }
  77.             }
  78.         }
  79.         cout << ans << '\n';
  80.     }
  81.     return 0;
  82. }
复制代码

四、关键知识点详解
  • 模逆元计算:使用费马小定理和快速幂
  • 多项式系数:多重集的排列数公式
  • 预处理技巧:O(n)时间预处理阶乘和逆阶乘
  • 边界处理:剩余奖品情况的分类讨论

五、复杂度分析
  • 预处理:O(max_n)
  • 每个测试用例:O(M)
  • 总复杂度:O(max_n + T*M)

六、实际应用场景七、学习建议转自:2023年GESP八级考题解析:奖品分配的组合数学解法

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

SEO网站 ( 冀ICP备2025113422号-2 )|网站地图

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表