洛谷·[国家集训队]Crash的数字表格 / JZPTAB

初见安~这里是传送门:洛谷P1829 [国家集训队]Crash的数字表格 / JZPTAB

题解

莫反好题。比较入门。

我们先假设n<=m。

\sum_{i=1}^n\sum_{j=1}^mlcm(i,j)\\ =\sum_{i=1}^n\sum_{j=1}^m \frac{i*j}{gcd(i,j)}\\ =\sum_{i=1}^n\sum_{j=1}^m\sum_{d=1}^n\frac{i*j}{d}[gcd(i,j)=d]\\ =\sum_{d=1}^nd\sum_{i=1}^{\lfloor \frac{n}{d} \rfloor}\sum_{j=1}^{\lfloor \frac{m}{d} \rfloor}i*j[gcd(i,j)=1]

容易发现此时后面的这一坨可以拿来莫反。为了方便推,我们先替换一下。

=\sum_{d=1}^nd*sum(\lfloor \frac{n}{d} \rfloor, \lfloor \frac{m}{d} \rfloor)

接下来开始莫反。

sum(n, m)=\sum_{i=1}^n\sum_{j=1}^mi*j[gcd(i,j)=1]\\ =\sum_{d=1}^n \mu(d)*d^2\sum_{i=1}^{\lfloor \frac{n}{d} \rfloor}\sum_{j=1}^{\lfloor \frac{m}{d} \rfloor}i*j\\

比较显然的是后面的两个求和符号可以等差数列\mathcal{O}(1)求,以及因为关于下取整d,所以可以进行整除分块。

回到原式,枚举d,送进sum去运算,这里也可以整除分块一次。所以进行两次整除分块即可。一次分块根号的复杂度,套起来过后复杂度大概\mathcal{O}(n+m)

代码很好写。

#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——

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值