题目
重点:在下面的解答中默认 n < = m n<=m n<=m
前置知识
莫比乌斯函数与欧拉函数
莫比乌斯反演
熟练地和式运算
整除分块
迪利克雷卷积
分析
步骤一:转化答案
很容易想起这道题:仪仗队
从 ( 0 , 0 ) (0,0) (0,0)到 x , y ) x,y) x,y)之间经过的点数 = g c d ( x , y ) − 1 =gcd(x,y)-1 =gcd(x,y)−1
为什么呢?
因为xy同时乘除某一个数后的点仍在 ( x , y ) (x,y) (x,y)与原点的连线上,所以假设最大公因数是d,那么
( x / d , y / d ) ∗ 1 … … d − 1 (x/d,y/d)*1……{d-1} (x/d,y/d)∗1……d−1这 d − 1 d-1 d−1个点均在 ( 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)} 2∗∑i=1n∑j=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) =2∗∑i=1n∑j=1m(gcd(i,j))−(n∗m)
更改枚举方式
= 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 =2∗∑d=1n∑d∣in∑d∣jm[gcd(i,j)=d]d−m∗n
= 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 =2∗∑d=1n∑d∣in/d∑d∣jm/d[gcd(i,j)=1]d−m∗n
用莫比乌斯函数的一个性质:
∑ d ∣ n μ ( d ) = 1 ? n = 1 : 0 \sum_{d|n}{μ(d)}=1?n=1:0 ∑d∣nμ(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 =2∗∑d=1nd∑d∣in/d∑d∣jm/d∑k∣gcd(i,j)μ(k)−m∗n
更改枚举顺序(稍微跳跃了一下,因为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/kd∑j=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 =2∗∑d=1nd∑k=1n/dμ(k)∗(n/kd)∗(m/kd)−m∗n
其实这样就可以计算了,预处理μ函数,枚举d然后做整除分块,时间复杂度 o ( n ∗ n ) o(n*\sqrt{n}) o(n∗n)
= 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 =2∗∑d=1nd∑k=1n/dμ(k)∗(n/kd)∗(m/kd)−m∗n
步骤三:卷积优化
但是这个还是会超时,那么就要用到一个小技巧
这个技巧在这道题里面也出现了:YY的GCD
可以叫他“还原”
之前我们只是改变了枚举方式,没有改变枚举的对象,然后我们又发现了 φ = i d ∗ μ φ=id*μ φ=id∗μ
也就是 φ ( n ) = ∑ d ∣ n d ∗ μ ( n / d ) φ(n)=\sum_{d|n}{d*μ(n/d)} φ(n)=∑d∣nd∗μ(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 =2∗∑d=1nd∑d∣Tn(μ(T/d)∗(n/T)∗(m/T))−m∗n
看见胜利的曙光了!
把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 =2∗∑d=1n∑d∣Tn(μ(T/d)∗d∗(n/T)∗(m/T))−m∗n
改变枚举顺序:先枚举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 =2∗∑T=1n∑d∣T(μ(T/d)∗d∗(n/T)∗(m/T))−m∗n
那两个整除和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 =2∗∑T=1n(n/T)∗(m/T)∑d∣T(μ(T/d)∗d)−m∗n
卷积转化
= 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 =2∗∑T=1n(phi(T)∗(n/T)∗(m/T))−m∗n
这样式子就推好了
卷积证明:
这个证明是写给初学卷积的读者的,所以讲的尽量详细。
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)=∑d∣nμ(d)∗I(n/d)=∑d∣nμ(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)=∑d∣nφ(d)∗I(n/d)=∑d∣nφ(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]} ∑d∣nμ(d)=[n=1]
这个结论看似简单但可以把未知转化为已知。
第三步核心就是卷积转化,还有一个更改枚举对象的技巧。