1 条题解

  • 0
    @ 2026-10-26 5:03:37

    思路与算法

    令 M=R−L+1M=R-L+1 为可用值种数。单调不降序列完全由每种值出现的次数 c1,…,cMc_1,\ldots,c_M 决定,这些次数均非负,总和是序列长度。为了把“长度至多 NN”改成等式,引入松弛量 s=N−∑ci≥0s=N-\sum c_i\ge0,得到 M+1M+1 个非负变量之和为 NN。

    插板法给出 (N+MM)\binom{N+M}{M} 个频数与松弛量向量;其中全频数为0对应空序列,必须减去1。因此答案为 (N+MM)−1\binom{N+M}{M}-1 模1000003。

    Lucas定理把大组合数拆成素数 PP 进制的数位乘积:

    (nm)≡∏j(njmj)(modP).\binom nm\equiv\prod_j\binom{n_j}{m_j}\pmod P.

    先预处理 0,…,P−10,\ldots,P-1 的阶乘和逆阶乘。每一位都小于 PP,对应阶乘非零,若 mj>njm_j>n_j 就返回0,否则乘上该位的小组合数。然后两参数整除 PP,直到都为0。这里 P=1000003P=1000003,N+M≤2×109N+M\le2\times10^9。用64位计算宽度及参数和,按Lucas得到组合数余数后规范化减1。

    正确性说明

    给定一个合法非空单调不降序列,统计各值出现次数,再补上唯一松弛量,得到和为 NN 的非负整数向量。反过来,从任意这样的向量按值从小到大重复输出相应次数,得到唯一长度不超过 NN 的单调不降序列。两者一一对应,唯一的空序列对应全部频数为0,所以插板总数恰减1。

    大组合数在素模下由Lucas拆成数位乘积,各位小阶乘可逆,故求得组合数余数。最后的加模再减1与整数计数减去空序列同余,得到所求结果。

    复杂度

    令 P=1000003P=1000003。初始化与阶乘、逆阶乘预处理 O(P)O(P),模逆快速幂 O(log⁡P)O(\log P)。单组查询为 O(log⁡P(N+M+1))O(\log_P(N+M+1)) 位,含全部输入总时间 O(P+log⁡P+T+∑qlog⁡P(Nq+Mq+1))O(P+\log P+T+\sum_q\log_P(N_q+M_q+1))。额外空间 O(P)O(P),没有按区间长度或 NN 建表。

    易错点

    1. 单调不降允许相等,不能按严格递增子集计数;只有区间宽度影响计数,端点平移不改变答案。
    2. 空序列不在长度1至 NN 中,组合数总数要减1。
    3. Lucas余数为0时,减1的规范化结果是 P−1P-1,不是负数;真实非空方案数的余数也可能为0。
    4. L=RL=R 时每种长度恰有一种序列,答案为 N mod PN\bmod P;N=1N=1 时答案为 M mod PM\bmod P。
    5. 覆盖最大参数、数位进位与最多100组输入。候选含N=0或L=0的整组测试文件不满足当前源域,已排除且未修改原包。独立答案用p进阶乘单位方法及小范围完整多重集合枚举验证。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    const int P = 1000003;
    ll power(ll a, ll e) {
        ll result = 1;
        while (e) {
            if (e & 1)
                result = result * a % P;
            a = a * a % P;
            e >>= 1;
        }
        return result;
    }
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        vector<ll> fact(P), inv(P);
        fact[0] = 1;
        for (int i = 1; i < P; i++)
            fact[i] = fact[i - 1] * i % P;
        inv[P - 1] = power(fact[P - 1], P - 2);
        for (int i = P - 1; i; i--)
            inv[i - 1] = inv[i] * i % P;
        auto lucas = [&](ll n, ll m) -> ll {
            ll answer = 1;
            while (n || m) {
                int a = n % P, b = m % P;
                if (b > a)
                    return 0;
                answer = answer * fact[a] % P * inv[b] % P * inv[a - b] % P;
                n /= P;
                m /= P;
            }
            return answer;
        };
        int tests;
        cin >> tests;
        while (tests--) {
            ll n, l, r;
            cin >> n >> l >> r;
            ll width = r - l + 1;
            cout << (lucas(n + width, width) - 1 + P) % P << '\n';
        }
    }
    
    • 1

    信息

    ID
    1670
    时间
    1000ms
    内存
    512MiB
    难度
    提高
    标签
    递交数
    1
    已通过
    1
    上传者