1 条题解

  • 0
    @ 2026-10-26 1:48:52

    思路与算法

    用F(t)返回1..t中十类数字各自出现次数,最终逐项计算F(b)-F(a-1)。

    从高位向低位做数位DP,状态包含剩余位位置pos、是否已经出现有效数位started,以及是否仍贴上界tight。每个状态同时返回ways(可形成的正整数数量)和count[0..9](这些数在尚需构造部分中的数字出现次数)。

    枚举当前数字d后,先累加子状态的ways和count;若当前数字已经是有效数字,还要让count[d]增加child.ways,因为每个子状态方案都各贡献一次当前的d。尚未开始且放0时,不统计这个补位0。结束时,已开始则ways=1,否则ways=0,排除整数0。只缓存非tight状态。

    正确性说明

    每个数位选择确定一组互不重叠的后缀方案。子状态的count准确统计后缀中出现的数字;当前有效数字在每个后缀方案中恰出现一次,增加child.ways正好补齐这一位的贡献。未开始的补位0不属于实际十进制表示,所以不计数。

    按剩余位数归纳,每个状态返回的方案数和十类次数都正确。tight只保留不超过上界的前缀,结束条件只计正整数,故F(t)准确统计1..t。逐项相减便得到a..b的十类出现次数。

    复杂度

    设十进制位数L≤13。每个非受限状态由位置和started决定,每次枚举10个数字并合并10项统计,总时间O(L×10²),空间O(L×10)。程序使用20×2个Stats缓存对象,启动时初始化各自10项计数;每次前缀统计清空40个ready标志,这些初始化成本均已包含在上述界内。

    易错点

    数字0的出现次数不能把前导补0算进去。必须同时保留方案数ways,否则无法正确加入当前位的贡献。端点可达10^12,统计结果可能超过32位,使用long long。输出顺序固定为0到9,共10个整数;F(0)应全为0。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    struct Stats {
        ll ways;
        array<ll, 10> count;
        Stats() : ways(0) {
            count.fill(0);
        }
    };
    vector<int> digit;
    Stats memo[20][2];
    bool ready[20][2];
    Stats solve(int pos, bool started, bool tight) {
        if (pos < 0) {
            Stats base;
            base.ways = started;
            return base;
        }
        if (!tight && ready[pos][started])
            return memo[pos][started];
        Stats answer;
        int cap = tight ? digit[pos] : 9;
        for (int d = 0; d <= cap; ++d) {
            bool now = started || d != 0;
            Stats child = solve(pos - 1, now, tight && d == cap);
            answer.ways += child.ways;
            for (int v = 0; v < 10; ++v)
                answer.count[v] += child.count[v];
            if (now)
                answer.count[d] += child.ways;
        }
        if (!tight) {
            ready[pos][started] = true;
            memo[pos][started] = answer;
        }
        return answer;
    }
    Stats count(ll n) {
        if (n <= 0)
            return Stats();
        digit.clear();
        while (n) {
            digit.push_back(n % 10);
            n /= 10;
        }
        memset(ready, 0, sizeof(ready));
        return solve(digit.size() - 1, false, true);
    }
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        ll a, b;
        cin >> a >> b;
        Stats high = count(b), low = count(a - 1);
        for (int d = 0; d < 10; ++d)
            cout << high.count[d] - low.count[d] << (d == 9 ? '\n' : ' ');
    }
    
    • 1

    信息

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