把区间和转成数根前缀和,利用数根序列以 9 为周期循环。
OJ: luogu
题目 ID: P7199
难度:普及-
标签:数学模拟
日期: 2026-06-18 21:10
题意
原序列满足 A_i = i。
现在把每个数不断替换成数位和,直到只剩下一位数。
对每个询问 [l, r],要求输出这一段最终值的总和。
思路
先看一个可以直接验证想法的朴素解:
逐个枚举区间里的数,对每个数反复做数位和,再累加。
cpp
#include <bits/stdc++.h>
using namespace std;
unsigned long long digit_root(unsigned long long x) {
while (x >= 10) {
unsigned long long sum = 0;
while (x > 0) {
sum += x % 10;
x /= 10;
}
x = sum;
}
return x;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int q;
cin >> q;
while (q--) {
unsigned long long l, r;
cin >> l >> r;
unsigned long long ans = 0;
for (unsigned long long x = l; x <= r; x++) {
ans += digit_root(x);
}
cout << ans << '\n';
}
return 0;
}真正的关键是认出这里的最终值其实就是“数根”。 正整数的数根序列按下面的模式循环:
text
1,2,3,4,5,6,7,8,9,1,2,3,...所以每 9 个数的贡献固定是 45。
设前缀和为 S(x),那么:
text
S(x) = (x / 9) * 45 + 1 + 2 + ... + (x % 9)这样区间答案就能直接写成:
text
S(r) - S(l - 1)代码
cpp
#include <bits/stdc++.h>
using namespace std;
unsigned long long prefix_sum(unsigned long long x) {
unsigned long long block = x / 9;
unsigned long long rem = x % 9;
return block * 45 + rem * (rem + 1) / 2;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int q;
cin >> q;
while (q--) {
unsigned long long l, r;
cin >> l >> r;
cout << prefix_sum(r) - prefix_sum(l - 1) << '\n';
}
return 0;
}复杂度
每个询问
总结
这题的关键不是数位和本身,而是发现“最终结果是数根,而且按 9 周期循环”。