SP17247
我的方法好像和大家都不一样诶,那我就写个题解吧。
约定:
记 \([a,b]\) 为区间 \([a,b]\) 中每个数的数字和的和,\([a,b)\) 为区间 \([a,b)\) 中每个数的数字和的和。
分析:
首先将区间问题转化为前缀和问题,即 \([a,b]=[0,b]-[0,a-1]=[0,b+1)-[0,a)\)。
也就是说我们只需要能求出任意的 \([0,n]=[0,n+1)\) 就可以了。
下面考虑如何求 \([0,n)\)。
求解:
不难发现,如果我们把 \(n\) 的按每一位拆开,比如如果 \(n=4325\),则显然有 \([0,4325)=[0,4000)+[4000,4300)+[4300,4320)+[4320,4325)\)。
形式化地说,如果令 \(n=\sum\limits_{i=0}^{\left\lfloor\lg n\right\rfloor}a_i10^i\,\,(a_i\in\mathbb N^*)\),则必然有:
\[[0,n)=\sum_{i=0}^{\left\lfloor\lg n\right\rfloor}[n-\sum_{j=0}^ia_i,n-\sum_{j=0}^{i-1}a_i)
\]
(好像有点太形式化了)
我们考虑如何求 \([43000,43200)\):
\[\begin{aligned}
[43000,43200)&=(4+3+0+0+0)+(4+3+0+0+1)+\dots+(4+3+1+9+9)
\\&=2\times 100\times(4+3)+2\times(0+1+2+\dots99)+10\times(0+1)
\\&=2\times 100\times(4+3)+2\times[0,100)+100\times[0,1]
\end{aligned}\]
我们发现,只要预处理求出 \(\{a\},[0,10^i)\) 和 \([0,i]\,(0\le i\le 9)\),然后按从例子中发现的规律,从高往低挨位处理,就可以快速求出 \([0,n)\) 的值了。
代码:
代码中有更明确的计算方式,可能更好理解吧。
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s; i <= e; ++i)
#define per(i, s, e) for(int i = s; i >= e; --i)
#define F first
#define S second
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef __int128_t i128;
typedef __uint128_t u128;
typedef pair<int, int> pii;
ll pw10[20], g[10], s0[20]; // pw10[i]: 10的i次幂,g[i]: [0,i]或[0,i+1),s0[i]: [0,10^i)
void init() {
pw10[0] = 1;
rep(i, 1, 15) pw10[i] = pw10[i - 1] * 10;
rep(i, 1, 9) g[i] = g[i - 1] + i;
s0[1] = 45;
rep(i, 1, 15) s0[i] = s0[i - 1] * 10 + 45 * pw10[i - 1]; // [0,9]=45
}
ll calc(ll x) {
ll dg[20] = {}, ans = 0, j = 0;
while(x) {
dg[j++] = x % 10;
x /= 10;
}
ll s = 0;
per(i, j, 1) { // 核心部分
s += dg[i];
ans += s * dg[i - 1] * pw10[i - 1];
ans += dg[i - 1] * s0[i - 1];
if(dg[i - 1]) ans += g[dg[i - 1] - 1] * pw10[i - 1];
}
return ans;
}
int main() {
#ifdef ONLINE_JUDGE
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
init();
int t; cin >> t;
while(t--) {
ll a, b; cin >> a >> b;
cout << calc(b + 1) - calc(a) << endl;
}
return 0;
}

浙公网安备 33010602011771号