隔板法把单调不降序列计数化为多重组合数,再用 hockey-stick 恒等式把按长度求和折叠成单个组合数,最后用 Lucas 定理按 10^6+3 进制拆位求值。
OJ: roj
题目 ID: 1657
难度:提高+/省选-
标签:组合数学Lucas 定理逆元
创建: 2026-10-01 00:21
更新: 2026-10-07 13:50
形式化题目
给定整数
- 长度
满足 ; - 每个元素都在
内; - 序列单调不降。
正解
思路
第一步:固定长度,隔板法计数。 设值域大小
于是答案形如
第二步:hockey-stick 恒等式折叠求和。 把每项改写成
含
用样例 2(
| 长度 |
方案数 |
前缀和 |
|---|---|---|
观察重点:上表逐项累加到
第三步:Lucas 定理求大组合数取模。 剩下的问题是求
每一位的参数都小于
最终公式:
代码
Python 版:
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 00:21
# update_at: 2026-10-01 00:21
import sys
P = 10**6 + 3 # 模数,质数:小于它的阶乘模它非零,Lucas 分解可用
# 阶乘表:P 以内的 i! mod P,Lucas 每一位的组合数都靠它
fact = [1] * P
for i in range(1, P):
fact[i] = fact[i - 1] * i % P
def comb(n: int, k: int) -> int:
"""求 C(n, k) mod P:n 可达 2e9 超过 P,按 Lucas 定理按 P 进制逐位组合。"""
res = 1
while n or k:
a, b = n % P, k % P # 当前 P 进制位
if b > a: # 某一位下标超过上标,整项为 0
return 0
res = (res * fact[a] * pow(fact[b], P - 2, P) * pow(fact[a - b], P - 2, P)) % P
n, k = n // P, k // P
return res
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
T = next(data)
out: list[str] = []
for _ in range(T):
n, l, r = next(data), next(data), next(data)
m = r - l + 1 # 元素取值个数
# 按长度枚举求和得 hockey-stick 恒等式:Σ_{i=1..n} C(m+i-1, i) = C(m+n, n)
# 非空序列至少含一个元素,从"长度 0 的空序列"对应的那 1 项里扣掉
out.append(str((comb(m + n, n) - 1) % P))
print('\n'.join(out))
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 00:54
* update_at: 2026-10-06 00:54
*/
#include <cstdio>
typedef long long ll;
const ll P = 1000003; // 模数,质数,Lucas 分解的进制
ll fact[1000003]; // fact[i] = i! mod P,供 Lucas 每一位查表
// 快速幂:求 base^exp mod P,用于费马小定理求逆元
ll power(ll base, ll exp) {
ll res = 1;
base %= P;
while (exp > 0) {
if (exp & 1) {
res = res * base % P;
}
base = base * base % P;
exp >>= 1;
}
return res;
}
// 组合数 C(n, k) mod P:n 可达 2e9 超过 P,按 Lucas 定理逐位相乘
ll comb(ll n, ll k) {
ll res = 1;
while (n > 0 || k > 0) {
ll a = n % P; // 当前 P 进制位
ll b = k % P;
if (b > a) {
return 0; // 某一位下标超过上标,整项为 0
}
res = res * fact[a] % P;
res = res * power(fact[b], P - 2) % P;
res = res * power(fact[a - b], P - 2) % P;
n /= P;
k /= P;
}
return res;
}
int main() {
fact[0] = 1;
for (ll i = 1; i < P; i++) {
fact[i] = fact[i - 1] * i % P;
}
ll t;
scanf("%lld", &t);
while (t--) {
ll n, l, r;
scanf("%lld%lld%lld", &n, &l, &r);
ll m = r - l + 1; // 元素取值个数
// 逐长度求和由 hockey-stick 恒等式折叠为 C(m+n, n),
// 该式包含了长度为 0 的空序列,题目要求长度至少为 1,故减 1
ll ans = (comb(m + n, n) - 1 + P) % P;
printf("%lld\n", ans);
}
return 0;
}复杂度
- 时间:预处理阶乘表
( ),每组询问 Lucas 拆位 层、每层 次快速幂乘法,总计 。 - 空间:
存阶乘表。
总结
- 计数模型:单调不降序列 = 可重组合,隔板法得
。 - 关键一步:hockey-stick 恒等式把
项求和折叠为 ,这是本题从"不可算"到"可算"的分水岭。 - 求值工具:模数
为质数且小于组合数参数,Lucas 定理 + 阶乘表 + 费马逆元。 - 别忘了减
:恒等式把空序列也计了进去。