1 条题解
-
0
思路与算法
令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