初见安~这里是传送门:洛谷P1829 [国家集训队]Crash的数字表格 / JZPTAB
题解
莫反好题。比较入门。
我们先假设n<=m。
容易发现此时后面的这一坨可以拿来莫反。为了方便推,我们先替换一下。
接下来开始莫反。
比较显然的是后面的两个求和符号可以等差数列求,以及因为关于下取整d,所以可以进行整除分块。
回到原式,枚举d,送进sum去运算,这里也可以整除分块一次。所以进行两次整除分块即可。一次分块根号的复杂度,套起来过后复杂度大概。
代码很好写。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 10000007
using namespace std;
typedef long long ll;
const int mod = 20101009;
int read() {
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int n, m;
int pri[maxn], tot = 0, mu[maxn];
bool in[maxn];
int pre[maxn];
int sum(int n, int m) {
int res = 0;
for(int l = 1, r; l <= n; l = r + 1) {//第二次分块
r = min(n / (n / l), m / (m / l));
register int a = n / l, b = m / l;
res = (res + 1ll * (mu[r] - mu[l - 1] + mod) * (1ll * a * (a + 1) / 2 % mod) % mod * (1ll * b * (b + 1) / 2 % mod) % mod) % mod;
}
return res;
}
signed main() {
n = read(), m = read();
if(n > m) swap(n, m);
mu[1] = 1;
for(int i = 2; i <= m; i++) {//线筛mu
if(!in[i]) pri[++tot] = i, mu[i] = -1;
for(int j = 1; j <= tot && 1ll * i * pri[j] <= m; j++) {
in[i * pri[j]] = true;
mu[i * pri[j]] = -mu[i];
if(i % pri[j] == 0) {mu[i * pri[j]] = 0; break;}
}
}
//预处理前缀和
for(int i = 1; i <= m; i++) mu[i] = (mu[i - 1] + 1ll * mu[i] * i * i % mod) % mod;
int ans = 0;
for(int l = 1, r; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));//第一次分块
ans = (ans + 1ll * (l + r) * (r - l + 1) / 2 % mod * sum(n / l, m / l) % mod) % mod;
}
printf("%d\n", ans);
return 0;
}
迎评:)
——End——