题意
定义F(x) 表示x所有因子的和,让你对一个区间的F(x)求和。
数值的范围有1e12,但是区间长度1e6。
思路
可以严格地推倒,但是以开始得有个切入点。
我的切入点是求F的前缀和,假设记作G。
那么有下面的性质:
G
(
6
)
=
F
(
1
)
+
F
(
2
)
+
F
(
3
)
+
F
(
4
)
+
F
(
5
)
+
F
(
6
)
=
⌊
6
/
1
⌋
∗
1
+
⌊
6
/
2
⌋
∗
2
+
⌊
6
/
3
⌋
∗
3
+
⌊
6
/
4
⌋
∗
4
+
⌊
6
/
5
⌋
∗
5
+
⌊
6
/
6
⌋
∗
6
G(6) = F(1) + F(2) +F(3) + F(4) +F(5) + F(6) \\ = \lfloor 6/ 1 \rfloor * 1 + \lfloor 6 /2 \rfloor * 2 + \lfloor 6 / 3 \rfloor * 3 + \lfloor 6 / 4 \rfloor * 4 + \lfloor 6 / 5 \rfloor * 5 + \lfloor 6/ 6 \rfloor * 6
G(6)=F(1)+F(2)+F(3)+F(4)+F(5)+F(6)=⌊6/1⌋∗1+⌊6/2⌋∗2+⌊6/3⌋∗3+⌊6/4⌋∗4+⌊6/5⌋∗5+⌊6/6⌋∗6
熟悉数论分块的话,可以知道同一块的数字,向下取整是一样的,只要处理一个等差数列求和即可。
稍微爆了一下ll,但是没有溢出ull。
int ans = 0;
for(int l = 1, r = 0; l <= n; l++) {
r = n / (n / l);
// do something
}
代码
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
ll sigma(ll l, ll r) {
return (l + r) * (r - l + 1) / 2ull;
}
ll g(ll x) {
if (x == 0) return 0;
ll res = 0;
for (ll l = 1, r = 0; l <= x; l = r + 1) {
r = x / (x / l);
res += sigma(l, r) * (x / r);
}
return res;
}
int main() {
ll a, b;
scanf("%llu %llu", &a, &b);
ll ans = g(b) - g(a - 1);
printf("%llu\n", ans);
return 0;
}
比赛中的代码:
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
ll a, b;
ll calcER(ll l, ll r) {
return (l + r) * (r - l + 1) / 2ll;
}
ll calcPre(ll x) {
if (x == 0) return 0;
ll res = 0;
for (ll i = 0, old; i <= x;) {
old = i;
i = x / (x / (i + 1));
// printf("%lld ", i);
res += calcER(old + 1, i) * (x / i);
if(i == x) break;
}
// puts("");
return res;
}
ll scanll() {
char ch;
ll res = 0;
while(isdigit(ch = getchar())){
res = res * 10 + ch - '0';
}
return res;
}
ll stk[105], top;
void printll(ll v){
while(v) {
stk[top++] = v % 10;
v /= 10;
}
while(top) {
printf("%d", int(stk[--top]));
}
printf("\n");
}
int main() {
ll a, b;
// scanf("%lld %lld", &a, &b);
a = scanll();
b = scanll();
// printll(calcPre(b));
// printll(calcPre(a - 1));
ll ans = calcPre(b) - calcPre(a - 1);
printll(ans);
return 0;
}
/*
*/