ARTICLE · 1077646
2026IRC智能竞技大赛真题:大组合数取模解析
2026IRC智能竞技大赛真题:大组合数取模解析
赛事锚点:四川省第四届IRC智能竞技大赛(信息学赛项)|原创算法题 · C++/Python 双版 · 七考点拆解
一、赛题背景
在 IRC 智能竞技大赛的信息学赛项中,有这样一道经典计数题:赛场准备了 n 种不同口味的能量糖果排成一排,要求从中恰好选出 k 种装进一份礼盒。问:在模一个给定大素数 p 的意义下,合法的选法总数有多少?当 n、k 大到 1018 量级时,直接算阶乘显然会爆掉,这就需要用卢卡斯定理(Lucas Theorem)把大数按 p 进制逐位拆开再组合。
二、题目描述
输入三个整数 n, k, p(p 为素数,且 p ≤ 100000),求 C(n, k) mod p。数据范围:0 ≤ k ≤ n ≤ 1018。若 k > n,答案为 0。
- 输入
:一行三个整数 n、k、p。 - 输出
:一个整数,表示 C(n, k) 对 p 取模的结果。 - 样例
:输入 100 3 100003,输出161700(即 C(100,3)=161700,恰好小于 100003,无需取模)。
三、核心算法:卢卡斯定理
卢卡斯定理指出,对于素数 p:
C(n, k) ≡ C(n mod p, k mod p) × C(⌊n/p⌋, ⌊k/p⌋) (mod p)
也就是说,把 n、k 写成 p 进制后,每一位上的组合数相乘再模 p 即可。当 k=0 时递归终止返回 1。由于每一位的 n mod p、k mod p 都 < p,我们只需在 0..p-1 范围内预处理阶乘与阶乘逆元,就能 O(1) 求出小范围组合数 C(a,b) mod p,整体复杂度 O(p + logp n)。
四、参考代码(C++)
#include <bits/stdc++.h> using namespace std; typedef long long ll; ll MOD; vector<ll> fact, invfact; // 快速幂:a^b mod MOD ll power(ll a, ll b) { ll res = 1 % MOD; a %= MOD; while (b) { if (b & 1) res = res * a % MOD; a = a * a % MOD; b >>= 1; } return res; } // 预处理阶乘与逆元阶乘(p 为给定素数,p <= 1e5) void build(ll p) { MOD = p; fact.assign(p, 1); for (ll i = 1; i < p; ++i) fact[i] = fact[i - 1] * i % MOD; invfact.assign(p, 1); invfact[p - 1] = power(fact[p - 1], MOD - 2); for (ll i = p - 1; i > 0; --i) invfact[i - 1] = invfact[i] * i % MOD; } // 小范围组合数 C(n, k) mod MOD(n, k < MOD) ll comb(ll n, ll k) { if (k < 0 || k > n) return 0; return fact[n] * invfact[k] % MOD * invfact[n - k] % MOD; } // 卢卡斯定理:把 n、k 按 p 进制逐位拆分 ll lucas(ll n, ll k) { if (k == 0) return 1; return comb(n % MOD, k % MOD) * lucas(n / MOD, k / MOD) % MOD; } int main() { ll n, k, p; cin >> n >> k >> p; // n, k <= 1e18;p 为素数且 p <= 1e5 build(p); cout << lucas(n, k) << endl; return 0; }五、参考代码(Python)
def solve(n, k, p): # 预处理阶乘与逆元阶乘(p 为给定素数,p <= 1e5) fact = [1] * p for i in range(1, p): fact[i] = fact[i - 1] * i % p invfact = [1] * p invfact[p - 1] = pow(fact[p - 1], p - 2, p) for i in range(p - 1, 0, -1): invfact[i - 1] = invfact[i] * i % p def comb(n, k): if k < 0 or k > n: return 0 return fact[n] * invfact[k] % p * invfact[n - k] % p def lucas(n, k): if k == 0: return 1 return comb(n % p, k % p) * lucas(n // p, k // p) % p return lucas(n, k) # 示例:从 100 个里选 3 个,模 100003 print(solve(100, 3, 100003)) # 输出 161700六、考点拆解(7 个)
- 组合恒等式
:C(n,k)=C(n,n-k),取较小值可减半枚举。 - 模逆元
:用费马小定理 pow(den, p-2, p) 求分母逆元(p 为素数)。 - 阶乘预处理
:O(p) 预处理 fact 与 invfact,小范围组合数 O(1) 查询。 - 卢卡斯拆分
:n、k 同时除以 p 递归,把大数拆成 p 进制位。 - 越界判定
:k>n 或 k<0 时组合数为 0,必须在 comb 里先判断。 - 数据范围
:n,k 用 long long / 大整数,避免乘法溢出。 - k=0 终止
:递归边界返回 1,否则会死循环或算错。
七、互动引导
🤔 想一想:如果题目要求计算 C(n,k) mod p 但 p 不是素数(比如合数),费马小定理就不成立了,这时该用什么方法?欢迎在评论区聊聊你的思路,也可以把本题改编成"从 n 排糖果里每行选 1 颗"的二维版本挑战一下!
📚 免费少儿编程资料(夸克网盘领取)
以下资料来自夸克网盘分享。个人号链接暂不支持直接点击,请长按或复制下方链接,打开夸克网盘 App / 网页粘贴即可保存:
1. 全国青少年信息素养大赛复赛集训题目Python&C++.docx https://pan.quark.cn/s/93995d3cb150 2. 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf https://pan.quark.cn/s/da97b5dbf75d 3. Python背记手册.pdf https://pan.quark.cn/s/7568ae9ca92b 4. Python课程 https://pan.quark.cn/s/a94bf02d00c6 5. 2024信息素养大赛图形化复赛集训题答案3-9 https://pan.quark.cn/s/6ccab7ec3cbc 6. 2025年03月份电子学会考级真题 https://pan.quark.cn/s/4403c4228912 7. 2025全国青少年信息素养大赛赛项说明 https://pan.quark.cn/s/d9d0df4a9f29 8. 青少儿信息素养大赛编程资料 https://pan.quark.cn/s/4ab6bd83be8a
资料持续更新,关注本号第一时间获取新分享。