1 条题解

  • 0
    @ 2026-10-26 2:29:30

    思路与算法

    令F(t)表示1..t中与7无关的数的平方和,答案是(F(R)-F(L-1))模M,其中M=10^9+7。

    从高位向低位做数位DP,状态是剩余位位置pos、已有前缀的数位和模7、已有前缀的数值模7,以及是否仍贴着上界tight。枚举数字时跳过7,并更新两种余数。全部位结束后,两种余数都非0才计一个合法方案。

    每个状态返回三项:合法后缀方案数C、尚未构造部分的数值之和S、平方和Q。若当前数字d的位权贡献为w=d10^pos,子状态的低位值为x,则合并贡献为C、S+wC、Q+2wS+wwC。根状态返回的平方和就是F(t)。三项均及时取模,非tight状态按位置和两种余数缓存。

    前导补0不含数字7,也不改变两种余数;全0表示最终因余数为0被拒绝,所以不必另外保存started标志。

    正确性说明

    数位递归逐位枚举所有不含7且不超过上界的表示,tight防止越界,两个余数准确记录当前前缀信息。结束时的检查恰好排除数位和或数值是7倍数的情况。因此合法正整数恰好各对应一个完成方案。

    给定当前位后,每个子方案的数值由低位x变为w+x。恒等式(w+x)^2=w^2+2wx+x^2说明三项合并式精确统计全部完成方案的个数、和与平方和。按剩余位数归纳,返回的统计正确。模运算满足加法和乘法的分配律,提前取模不改变最终模平方和。前缀相减便得到闭区间答案。

    复杂度

    设最大十进制位数L≤19,测试组数T≤50。每次前缀统计有O(L7^2)个非受限状态,每个至多10次转移。缓存与ready数组上限为207^2,每次前缀还整体清空ready。包含初始化,总时间O(20+T*(207^2+L7^210)),空间O(207^2+L);每个状态的三项统计为固定大小。

    易错点

    必须同时检查三条禁用条件,不能直接复用只排除数字7和7倍数的小范围题。平方项中的w*w应先取模,再乘方案数;代码中各个乘积都在long long安全范围内。区间相减可能为负,要加M后再模M。F(0)=0,包含7的单点区间结果应为0。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    const ll MOD = 1000000007;
    struct Stats {
        ll count = 0, sum = 0, square = 0;
    };
    vector<int> digit;
    ll power10[20];
    Stats memo[20][7][7];
    bool ready[20][7][7];
    Stats solve(int pos, int sum7, int value7, bool tight) {
        if (pos < 0) {
            Stats r;
            r.count = sum7 != 0 && value7 != 0;
            return r;
        }
        if (!tight && ready[pos][sum7][value7])
            return memo[pos][sum7][value7];
        Stats r;
        int cap = tight ? digit[pos] : 9;
        for (int d = 0; d <= cap; ++d) {
            if (d == 7)
                continue;
            Stats child = solve(pos - 1, (sum7 + d) % 7, (value7 * 10 + d) % 7, tight && d == cap);
            ll place = power10[pos] * d % MOD;
            r.count = (r.count + child.count) % MOD;
            r.sum = (r.sum + child.sum + place * child.count) % MOD;
            r.square = (r.square + child.square + 2 * place % MOD * child.sum +
                        place * place % MOD * child.count) %
                       MOD;
        }
        if (!tight) {
            ready[pos][sum7][value7] = true;
            memo[pos][sum7][value7] = r;
        }
        return r;
    }
    ll count(ll n) {
        if (n <= 0)
            return 0;
        digit.clear();
        while (n) {
            digit.push_back(n % 10);
            n /= 10;
        }
        memset(ready, 0, sizeof(ready));
        return solve(digit.size() - 1, 0, 0, true).square;
    }
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        power10[0] = 1;
        for (int i = 1; i < 20; ++i)
            power10[i] = power10[i - 1] * 10 % MOD;
        int T;
        cin >> T;
        while (T--) {
            ll l, r;
            cin >> l >> r;
            ll high = count(r), low = count(l - 1);
            cout << (high - low + MOD) % MOD << '\n';
        }
    }
    
    • 1

    信息

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