1 条题解

  • 0
    @ 2026-10-26 12:26:00

    思路与算法

    同样用前缀函数F(x)做区间差。状态记录位置、当前数位和对N的余数以及tight;每加一位d,余数变为(rem+d)%N。填完时只在余数0计一次。前导0不改变数位和,所以无需另设开始标志。原文分别约束a,b但未写大小顺序;按闭区间集合定义,当a>b时区间为空,返回0。

    正确性说明

    余数状态在每个前缀后都等于其数字和模N,因此终态为0当且仅当整个数符合要求。逐位枚举与上界标志保证每个0至x的整数恰好被计一次,且无超上界路径。0的数位和是0,在两个前缀中同被计入而相消。若a>b,不存在同时满足a<=x<=b的整数,空区间计数0正确。

    复杂度

    计算状态O(LN),每个试10个数码,时间O(LN10)。每次前缀查询还初始化固定11100=1100个缓存槽,完整开销为O(1100+LN10),空间O(1100+L),包括递归栈。最多10位,N在1至99。含0的前缀计数在最大上界可为2^31,超过signed32,使用long long。

    易错点

    条件是数位和模N为0,不是原数模N。N=1时区间内所有正数都满足;N大于最大可达数位和时可能没有任何正数。每个区间的N都可能不同,缓存必须重置。空区间输出0,不交换输入端点改变集合。独立答案先求精确数位和的多项式系数分布,再按前缀枚举统计,不复用余数状态递归。

    参考代码(C++11)

    #include <cstring>
    #include <iostream>
    #include <string>
    using namespace std;
    string digits;
    int mod;
    long long memo[11][100];
    long long visit(int pos, int remainder, bool tight) {
        if (pos == (int)digits.size())
            return remainder == 0;
        if (!tight && memo[pos][remainder] >= 0)
            return memo[pos][remainder];
        int upper = tight ? digits[pos] - '0' : 9;
        long long ans = 0;
        for (int d = 0; d <= upper; ++d)
            ans += visit(pos + 1, (remainder + d) % mod, tight && d == upper);
        if (!tight)
            memo[pos][remainder] = ans;
        return ans;
    }
    long long count(long long x) {
        if (x < 0)
            return 0;
        digits = to_string(x);
        memset(memo, -1, sizeof(memo));
        return visit(0, 0, true);
    }
    int main() {
        long long a, b;
        while (cin >> a >> b >> mod) {
            if (a > b)
                cout << 0 << '\n';
            else
                cout << count(b) - count(a - 1) << '\n';
        }
    }
    
    • 1

    信息

    ID
    1723
    时间
    1000ms
    内存
    512MiB
    难度
    普及
    标签
    递交数
    1
    已通过
    1
    上传者