P1829 [国家集训队]Crash的数字表格 / JZPTAB

(color{#0066ff}{ 题目描述 })

今天的数学课上,Crash小朋友学习了最小公倍数(Least Common Multiple)。对于两个正整数a和b,LCM(a, b)表示能同时整除a和b的最小正整数。例如,LCM(6, 8) = 24。

回到家后,Crash还在想着课上学的东西,为了研究最小公倍数,他画了一张NM的表格。每个格子里写了一个数字,其中第i行第j列的那个格子里写着数为LCM(i, j)。一个45的表格如下:

1    2    3    4    5
2    2    6    4    10
3    6    3    12    15
4    4    12    4    20

看着这个表格,Crash想到了很多可以思考的问题。不过他最想解决的问题却是一个十分简单的问题:这个表格中所有数的和是多少。当N和M很大时,Crash就束手无策了,因此他找到了聪明的你用程序帮他解决这个问题。由于最终结果可能会很大,Crash只想知道表格里所有数的和mod20101009的值。

(color{#0066ff}{输入格式})

输入的第一行包含两个正整数,分别表示N和M。

(color{#0066ff}{输出格式})

输出一个正整数,表示表格中所有数的和mod20101009的值。

(color{#0066ff}{输入样例})

4 5

(color{#0066ff}{输出样例})

122

(color{#0066ff}{数据范围与提示})

30%的数据满足(N, M≤ 10^3。)

70%的数据满足(N, M≤ 10^5。)

100%的数据满足(N, M≤ 10^7。)

(color{#0066ff}{ 题解 })

题目就是要求

[sum_{i=1}^n sum_{j=1}^m lcm(i,j) ]

显然要转为gcd的东西

[sum_{i=1}^n sum_{j=1}^m frac {i*j} {gcd(i,j)} ]

gcd套路,考虑枚举gcd

[sum_{d=1}^{min(n,m)} sum_{i=1}^n sum_{j=1}^m [gcd(i,j)==d] frac {i*j} {d} ]

老套路,把d除上去,注意后面的(i*j),各除去了一个d,所以要乘回来(d^2)

[sum_{d=1}^{min(n,m)} sum_{i=1}^{lfloorfrac{n}{d} floor} sum_{j=1}^{lfloorfrac{m}{d} floor} [gcd(i,j)==1] i*j*d ]

然后发现d可以提出来

[sum_{d=1}^{min(n,m)} dsum_{i=1}^{lfloorfrac{n}{d} floor} sum_{j=1}^{lfloorfrac{m}{d} floor} [gcd(i,j)==1] i*j ]

我们令(f(x, y))等于后面那一坨,即

[f(a,b)=sum_{i=1}^{a} sum_{j=1}^{b} [gcd(i,j)==1] i*j ]

看到这种形式,继续利用(mu * i =e),来化简(f)

[sum_{i=1}^{a} sum_{j=1}^{b} sum_{d|gcd(i,j)} mu(d)* i*j ]

然后枚举gcd

[sum_{d=1}^{min(a,b)} mu(d) sum_{d|i}^{a} sum_{d|j}^b i*j ]

后面的显然不好枚举,考虑(i=p_1d,j=p_2d),枚举(p_1,p_2)

[sum_{d=1}^{min(a,b)} mu(d) sum_{i=1}^{lfloorfrac a k floor} sum_{j=1}^{lfloorfrac b k floor} i*d*j*d ]

然后我们又可以开心的把d提出来了

[sum_{d=1}^{min(a,b)} mu(d) * d^2 sum_{i=1}^{lfloorfrac a k floor} sum_{j=1}^{lfloorfrac b k floor} i*j ]

后面的东西好像很好求呢

[令g(a,b)=sum_{i=1}^asum_{j=1}^bi*j=frac{a*(a+1)}{2}*frac{b*(b+1)}{2} ]

嗯,(O(1))就出来了

至此,本题就完了

总结一下

[sum_{i=1}^n sum_{j=1}^m lcm(i,j)=sum_{d=1}^{min(n,m)} d *f(lfloorfrac{n}{d} floor,lfloorfrac{m}{d} floor) ]

[f(a,b)=sum_{d=1}^{min(a,b)} mu(d) * d^2* g(lfloorfrac a k floor,lfloorfrac b k floor) ]

[g(a,b)=frac{a*(a+1)}{2}*frac{b*(b+1)}{2} ]

最后一个(O(1))求,前两个数列分块即可

#include<bits/stdc++.h>
#define LL long long
LL in() {
	char ch; LL x = 0, f = 1;
	while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
	return x * f;
}
const int mod = 20101009;
const int maxn = 1e7 + 10;
LL pri[maxn], tot, mu[maxn], s[maxn];
bool vis[maxn];

void predoit() {
	mu[1] = 1;
	for(int i = 2; i < maxn; i++) {
		if(!vis[i]) pri[++tot] = i, mu[i] = -1;
		for(int j = 1; j <= tot && (LL)i * pri[j] < maxn; j++) {
			vis[i * pri[j]] = true;
			if(i % pri[j] == 0) break;
			else mu[i * pri[j]] = -mu[i];
		}
	}
	for(int i = 1; i < maxn; i++) s[i] = (s[i - 1] + 1LL * mu[i] * i * i) % mod;
}
LL getsum1(LL l, LL r) {
	return ((l + r) * (r - l + 1) >> 1LL) % mod;
}
LL getsum2(LL a, LL b) {
	return (((a * (a + 1) >> 1LL) % mod) * ((b * (b + 1) >> 1LL) % mod)) % mod;
}
LL getans(LL a, LL b) {
	LL ans = 0;
	for(LL l = 1, r; l <= std::min(a, b); l = r + 1) {
		r = std::min(a / (a / l), b / (b / l));
		(ans += ((((s[r] - s[l - 1]) % mod + mod) % mod) * getsum2(a / l, b / l) % mod)) %= mod;
	}
	return ans;
}


LL work(LL a, LL b) {
	LL ans = 0;
	for(LL l = 1, r; l <= std::min(a, b); l = r + 1) {
		r = std::min(a / (a / l), b / (b / l));
		(ans += getsum1(l, r) * getans(a / l, b / l) % mod) %= mod;
	}
	return ans;
}


int main() {
	predoit();
	int n = in(), m = in();
	printf("%lld
", work(n, m));
	return 0;
}
原文地址:https://www.cnblogs.com/olinr/p/10294278.html