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

题目背景

提示:原 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 = 1 n ∑ j = 1 m [ i , j ] = ∑ i = 1 n ∑ j = 1 m i ⋅ 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),则必有: ( i d , j d ) = 1 (\frac{i}{d},\frac{j}{d})=1 (di,dj)=1,于是:
= ∑ i = 1 n ∑ j = 1 m ∑ d ∣ i ∧ d ∣ j ∧ ( i d , j d ) = 1 i ⋅ j d =\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
考虑用 d d d枚举 i , j i,j i,j,则我们可以把和式提前:
= ∑ d = 1 min ⁡ ( n , m ) d ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ [ ( 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 = 1 n ∑ j = 1 m [ ( i , j = 1 ) ] ⋅ i ⋅ j \text{Sum}(n,m)=\sum_{i=1}^n\sum_{j=1}^m[(i,j=1)]\cdot i\cdot j Sum(n,m)=i=1nj=1m[(i,j=1)]ij
则:
ans = ∑ d = 1 min ⁡ ( n , m ) d ⋅ Sum ( ⌊ n d ⌋ , ⌊ m d ⌋ ) \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 = 1 min ⁡ ( n , m ) ∑ d ∣ i n ∑ d ∣ j m μ ( 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 = 1 min ⁡ ( n , m ) μ ( d ) d 2 ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ 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 = 1 n ∑ j = 1 m i j = ∑ i = 1 n i ∑ j = 1 m j = n ( n + 1 ) 2 × m ( m + 1 ) 2 L(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 = 1 min ⁡ ( n , m ) μ ( d ) d 2 L ( ⌊ n d ⌋ , ⌊ m d ⌋ ) \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)
于是可以数论分块求。

定一个前缀和,令:
S d = ∑ i = 1 d μ ( i ) i 2 S_d=\sum_{i=1}^dμ(i)i^2 Sd=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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值