莫比乌斯反演-能量采集题解

题目

题目链接

重点:在下面的解答中默认 n < = m n<=m n<=m

前置知识

莫比乌斯函数与欧拉函数

莫比乌斯反演

熟练地和式运算

整除分块

迪利克雷卷积

分析

步骤一:转化答案

很容易想起这道题:仪仗队

( 0 , 0 ) (0,0) 00 x , y ) x,y) xy之间经过的点数 = g c d ( x , y ) − 1 =gcd(x,y)-1 =gcdxy1

为什么呢?

因为xy同时乘除某一个数后的点仍在 ( x , y ) (x,y) xy与原点的连线上,所以假设最大公因数是d,那么

( x / d , y / d ) ∗ 1 … … d − 1 (x/d,y/d)*1……{d-1} x/dy/d1d1 d − 1 d-1 d1个点均在 ( x , y ) (x,y) (x,y)和原点连线上。

因此,答案即为 2 ∗ ∑ i = 1 n ∑ j = 1 m ( ( g c d ( i , j ) − 1 ) + 1 ) 2*\sum_{i=1}^{n}\sum_{j=1}^{m}{({(gcd(i,j)-1)}+1)} 2i=1nj=1m((gcd(i,j)1)+1)

步骤二:反演处理

= 2 ∗ ∑ i = 1 n ∑ j = 1 m ( g c d ( i , j ) ) − ( n ∗ m ) =2*\sum_{i=1}^{n}\sum_{j=1}^{m}{(gcd(i,j))}-(n*m) =2i=1nj=1m(gcd(i,j))(nm)

更改枚举方式

= 2 ∗ ∑ d = 1 n ∑ d ∣ i n ∑ d ∣ j m [ g c d ( i , j ) = d ] d − m ∗ n =2*\sum_{d=1}^{n}\sum_{d|i}^{n}\sum_{d|j}^{m}[gcd(i,j)=d]d-m*n =2d=1ndindjm[gcd(i,j)=d]dmn

= 2 ∗ ∑ d = 1 n ∑ d ∣ i n / d ∑ d ∣ j m / d [ g c d ( i , j ) = 1 ] d − m ∗ n =2*\sum_{d=1}^{n}\sum_{d|i}^{n/d}\sum_{d|j}^{m/d}[gcd(i,j)=1]d-m*n =2d=1ndin/ddjm/d[gcd(i,j)=1]dmn

用莫比乌斯函数的一个性质:

∑ d ∣ n μ ( d ) = 1 ? n = 1 : 0 \sum_{d|n}{μ(d)}=1?n=1:0 dnμ(d)=1?n=1:0

也就是说上面这个式子只要n=1的时候才是1,所以我们可以拿它替换任何 [ x = 1 ] [x=1] [x=1]这样的判断式

这也是为什么前面要把ij同时除以d

= 2 ∗ ∑ d = 1 n d ∑ d ∣ i n / d ∑ d ∣ j m / d ∑ k ∣ g c d ( i , j ) μ ( k ) − m ∗ n =2*\sum_{d=1}^{n}d\sum_{d|i}^{n/d}\sum_{d|j}^{m/d}\sum_{k|gcd(i,j)}{μ(k)}-m*n =2d=1nddin/ddjm/dkgcd(i,j)μ(k)mn

更改枚举顺序(稍微跳跃了一下,因为ij变成了 ∑ i = 1 n / k d ∑ j = 1 m / k d 1 \sum_{i=1}^{n/kd}\sum_{j=1}^{m/kd}1 i=1n/kdj=1m/kd1所以直接把它变成整除形式)

= 2 ∗ ∑ d = 1 n d ∑ k = 1 n / d μ ( k ) ∗ ( n / k d ) ∗ ( m / k d ) − m ∗ n =2*\sum_{d=1}^{n}d\sum_{k=1}^{n/d}{μ(k)*(n/kd)*(m/kd)}-m*n =2d=1ndk=1n/dμ(k)(n/kd)(m/kd)mn

其实这样就可以计算了,预处理μ函数,枚举d然后做整除分块,时间复杂度 o ( n ∗ n ) o(n*\sqrt{n}) o(nn )

= 2 ∗ ∑ d = 1 n d ∑ k = 1 n / d μ ( k ) ∗ ( n / k d ) ∗ ( m / k d ) − m ∗ n =2*\sum_{d=1}^{n}d\sum_{k=1}^{n/d}{μ(k)*(n/kd)*(m/kd)}-m*n =2d=1ndk=1n/dμ(k)(n/kd)(m/kd)mn

步骤三:卷积优化

但是这个还是会超时,那么就要用到一个小技巧

这个技巧在这道题里面也出现了:YY的GCD

可以叫他“还原”

之前我们只是改变了枚举方式,没有改变枚举的对象,然后我们又发现了 φ = i d ∗ μ φ=id*μ φ=idμ

也就是 φ ( n ) = ∑ d ∣ n d ∗ μ ( n / d ) φ(n)=\sum_{d|n}{d*μ(n/d)} φ(n)=dndμ(n/d)会这个结论证明的读者可以跳过,文章最后会给出证明

我们令T=kd,尽可能地把d和μ(n/d)放在一起

那么步骤二最后的式子:

= 2 ∗ ∑ d = 1 n d ∑ d ∣ T n ( μ ( T / d ) ∗ ( n / T ) ∗ ( m / T ) ) − m ∗ n =2*\sum_{d=1}^{n}{d}\sum_{d|T}^{n}(μ(T/d)*(n/T)*(m/T))-m*n =2d=1nddTn(μ(T/d)(n/T)(m/T))mn

看见胜利的曙光了!

把d放进去

= 2 ∗ ∑ d = 1 n ∑ d ∣ T n ( μ ( T / d ) ∗ d ∗ ( n / T ) ∗ ( m / T ) ) − m ∗ n =2*\sum_{d=1}^{n}\sum_{d|T}^{n}(μ(T/d)*{d}*(n/T)*(m/T))-m*n =2d=1ndTn(μ(T/d)d(n/T)(m/T))mn

改变枚举顺序:先枚举T

= 2 ∗ ∑ T = 1 n ∑ d ∣ T ( μ ( T / d ) ∗ d ∗ ( n / T ) ∗ ( m / T ) ) − m ∗ n =2*\sum_{T=1}^{n}\sum_{d|T}(μ(T/d)*{d}*(n/T)*(m/T))-m*n =2T=1ndT(μ(T/d)d(n/T)(m/T))mn

那两个整除和d没关系,提出来

= 2 ∗ ∑ T = 1 n ( n / T ) ∗ ( m / T ) ∑ d ∣ T ( μ ( T / d ) ∗ d ) − m ∗ n =2*\sum_{T=1}^{n}{(n/T)*(m/T)}\sum_{d|T}(μ(T/d)*{d})-m*n =2T=1n(n/T)(m/T)dT(μ(T/d)d)mn

卷积转化

= 2 ∗ ∑ T = 1 n ( p h i ( T ) ∗ ( n / T ) ∗ ( m / T ) ) − m ∗ n =2*\sum_{T=1}^{n}(phi(T)*(n/T)*(m/T))-m*n =2T=1n(phi(T)(n/T)(m/T))mn

这样式子就推好了

卷积证明:

这个证明是写给初学卷积的读者的,所以讲的尽量详细。

I(n):恒等函数,即 I ( n ) = 1 I(n)=1 I(n)=1

e(n):元函数 e ( n ) = [ n = 1 ] e(n)=[n=1] e(n)=[n=1]

i d ( n ) = n id(n)=n id(n)=n

μ ∗ I ( n ) = ∑ d ∣ n μ ( d ) ∗ I ( n / d ) = ∑ d ∣ n μ ( d ) = e ( n ) μ*I(n)=\sum_{d|n}{μ(d)*I(n/d)}=\sum_{d|n}{μ(d)}=e(n) μI(n)=dnμ(d)I(n/d)=dnμ(d)=e(n)

φ ∗ I ( n ) = ∑ d ∣ n φ ( d ) ∗ I ( n / d ) = ∑ d ∣ n φ ( d ) = n = i d ( n ) φ*I(n)=\sum_{d|n}{φ(d)*I(n/d)}=\sum_{d|n}{φ(d)}=n=id(n) φI(n)=dnφ(d)I(n/d)=dnφ(d)=n=id(n)

根据上面一条,左右同时×μ得到

φ ∗ e ( n ) = i d ∗ μ ( n ) ) φ*e(n)=id*μ(n)) φe(n)=idμ(n))

e在卷积中的地位和1在整数乘法中的地位一样,可以忽略

原结论得证

步骤四:代码实现

数论题目特点就是代码好实现,式子难推到

我们用整除分块的思想,枚举每一段,然后用线性筛筛出欧拉函数再预处理φ的前缀和即可

下附AC代码

#include<bits/stdc++.h>
using namespace std;
int read(){//根本没必要的快速读入 
	char s;
	int x=0,f=1;
	s=getchar();
	while(s<'0'||s>'9'){
		if(s=='-')f=-1;
		s=getchar();
	}
	while(s>='0'&&s<='9'){
		x*=10;
		x+=s-'0';
		s=getchar();
	}
	return x*f;
}
const int N=1e5+5;
long long phi[N],S[N];
bool flag[N];
int p[N],pn;
void init(int n){//预处理 
	phi[1]=1;
	for(int i=2;i<=n;i++){//线性筛 
		if(!flag[i]){
			p[pn++]=i;
			phi[i]=i-1;
		}
		for(int j=0;j<pn,p[j]*i<=n;j++){
			flag[p[j]*i]=1;
			if(i%p[j]==0){
				phi[i*p[j]]=phi[i]*p[j];
				break;
			}
			else{
				phi[i*p[j]]=phi[i]*phi[p[j]];
			}
		}
	}
	for(int i=1;i<=n;i++){//欧拉函数前缀和 
		S[i]=S[i-1]+phi[i];
	}
}
int main(){
	long long n,m;
	n=read(),m=read();
	if(n>m)swap(n,m);//这步别忘 
	init(1e5);
	long long ans=0;
	for(int l=1,r;l<=n;l=r+1){//整除分块 
		r=min(n/(n/l),m/(m/l));
		ans+=(long long)(S[r]-S[l-1])*(n/l)*(m/l);
	}
	ans=(long long)2*ans-(long long)(n*m);
	printf("%lld\n",ans);
}

总结

第一步中用gcd转化题意很妙

第二、三步中反复交换枚举顺序,初学者可以慢下来好好想想每步是不是对的,每次想想这么做的目的是什么

莫比乌斯函数的那个性质非常有用,一定要记住: ∑ d ∣ n μ ( d ) = [ n = 1 ] \sum_{d|n}{μ(d)=[n=1]} dnμ(d)=[n=1]

这个结论看似简单但可以把未知转化为已知。

第三步核心就是卷积转化,还有一个更改枚举对象的技巧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值