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

本文详细解析了一道数论问题的解题思路,利用莫比乌斯反演和数论分块技巧,求解一个N*M表格中所有数的最小公倍数之和的模运算结果。通过公式推导和代码实现,展示了高效的算法设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目背景

提示:原 P1829 半数集问题 已经迁移至 P1028 数的计算

题目描述

今天的数学课上,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的值。

输入输出格式

输入格式:

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

输出格式:

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

思路讲解

这是一道莫比乌斯反演和数论分块的练手题。

推一波公式:
ans=∑i=1n∑j=1m[i,j]=∑i=1n∑j=1mi⋅j(i,j)\text{ans}=\sum_{i=1}^n\sum_{j=1}^m[i,j]=\sum_{i=1}^n\sum_{j=1}^m\frac{i\cdot j}{(i,j)}ans=i=1nj=1m[i,j]=i=1nj=1m(i,j)ij
d=(i,j)d=(i,j)d=(i,j),则必有:(id,jd)=1(\frac{i}{d},\frac{j}{d})=1(di,dj)=1,于是:
=∑i=1n∑j=1m∑d∣i∧d∣j∧(id,jd)=1i⋅jd=\sum_{i=1}^n\sum_{j=1}^m\sum_{d|i∧d|j∧(\frac{i}{d},\frac{j}{d})=1}\frac{i\cdot j}{d}=i=1nj=1mdidj(di,dj)=1dij
考虑用ddd枚举i,ji,ji,j,则我们可以把和式提前:
=∑d=1min⁡(n,m)d∑i=1⌊nd⌋∑j=1⌊md⌋[(i,j)=1]⋅i⋅j=\sum_{d=1}^{\min(n,m)}d\sum_{i=1}^{\left\lfloor\frac{n}{d}\right\rfloor}\sum_{j=1}^{\left\lfloor\frac{m}{d}\right\rfloor}[(i,j)=1]\cdot i\cdot j=d=1min(n,m)di=1dnj=1dm[(i,j)=1]ij
我们令:
Sum(n,m)=∑i=1n∑j=1m[(i,j=1)]⋅i⋅j\text{Sum}(n,m)=\sum_{i=1}^n\sum_{j=1}^m[(i,j=1)]\cdot i\cdot jSum(n,m)=i=1nj=1m[(i,j=1)]ij
则:
ans=∑d=1min⁡(n,m)d⋅Sum(⌊nd⌋,⌊md⌋)\text{ans}=\sum_{d=1}^{\min(n,m)}d\cdot \text{Sum}(\left\lfloor\frac{n}{d}\right\rfloor,\left\lfloor\frac{m}{d}\right\rfloor)ans=d=1min(n,m)dSum(dn,dm)
考虑求Sum(n,m)\text{Sum}(n,m)Sum(n,m):

根据套路,套莫比乌斯反演:
=∑d=1min⁡(n,m)∑d∣in∑d∣jmμ(d)⋅i⋅j=\sum_{d=1}^{\min(n,m)}\sum_{d|i}^n\sum_{d|j}^mμ(d)\cdot i\cdot j=d=1min(n,m)dindjmμ(d)ij
可以将μ(d)μ(d)μ(d)提前:
=∑d=1min⁡(n,m)μ(d)d2∑i=1⌊nd⌋∑j=1⌊md⌋i⋅j=\sum_{d=1}^{\min(n,m)}μ(d)d^2\sum_{i=1}^{\left\lfloor\frac{n}{d}\right\rfloor}\sum_{j=1}^{\left\lfloor\frac{m}{d}\right\rfloor}i\cdot j=d=1min(n,m)μ(d)d2i=1dnj=1dmij
由拉格朗日插值公式,我们可以知道:
L(n,m)=∑i=1n∑j=1mij=∑i=1ni∑j=1mj=n(n+1)2×m(m+1)2L(n,m)=\sum_{i=1}^n\sum_{j=1}^mij=\sum_{i=1}^ni\sum_{j=1}^mj=\frac{n(n+1)}{2}×\frac{m(m+1)}{2}L(n,m)=i=1nj=1mij=i=1nij=1mj=2n(n+1)×2m(m+1)
于是:
Sum(n,m)=∑d=1min⁡(n,m)μ(d)d2L(⌊nd⌋,⌊md⌋)\text{Sum}(n,m)=\sum_{d=1}^{\min(n,m)}μ(d)d^2L(\left\lfloor\frac{n}{d}\right\rfloor,\left\lfloor\frac{m}{d}\right\rfloor)Sum(n,m)=d=1min(n,m)μ(d)d2L(dn,dm)
于是可以数论分块求。

定一个前缀和,令:
Sd=∑i=1dμ(i)i2S_d=\sum_{i=1}^dμ(i)i^2Sd=i=1dμ(i)i2
就可以了。

时间复杂度:

我估计是O(min⁡(n,m))\mathcal O(\min(n,m))O(min(n,m))的吧!有一定的常数。

#include <cstdio>
#include <iostream>

using namespace std;
const int N = 1E+7 + 1;
const int M = 20101009;
int S[N], prime[N/10], Mu[N], n, m, tot;
bool flag[N];

void sieve() {
	Mu[1] = 1;
	for(int i = 2; i <= min(n, m); i++) {
		if(!flag[i]) prime[++tot] = i, Mu[i] = -1;
		for(int j = 1; j <= tot && i * prime[j] <= min(n, m); j++) {
			flag[i * prime[j]] = 1;
			if(i % prime[j] == 0) { Mu[i * prime[j]] = 0; break; }
			Mu[i * prime[j]] = -Mu[i];
		}
	}
	for(int d = 1; d <= min(n, m); d++) 
		S[d] = (S[d - 1] + 1LL * d * d % M * (Mu[d] + M)) % M;
}
int G(int n, int m) {
	return (1LL * n * (n + 1) / 2 % M) * (1LL * m * (m + 1) / 2 % M) % M;
}
int Sum(int n, int m) {
	int res = 0;
	for(int l = 1, r; l <= min(n, m); l = r + 1) {
		r = min(n / (n / l), m / (m / l));
		res = (res + 1LL * (S[r] - S[l - 1] + M) * G(n / l, m / l) % M) % M;
	}
	return res;
}
int cal(int n, int m) {
	int res = 0;
	for(int l = 1, r; l <= min(n, m); l = r + 1) {
		r = min(n / (n / l), m / (m / l));
		res = (res + 1LL * (l + r) * (r - l + 1) / 2 % M * Sum(n / l, m / l) % M) % M;
	}
	return res;
}
int main()
{
	scanf("%d %d", &n, &m);
	sieve();
	printf("%d", cal(n, m));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值