-
题目描述:神犇YY虐完数论后给傻×kAc出了一题
给定N, M,求1<=x<=N, 1<=y<=M且gcd(x, y)为质数的(x, y)有多少对
kAc这种傻×必然不会了,于是向你来请教……
多组输入(大概也就一万组吧……)
-
解决本题需要了解莫比乌斯反演的知识,所以先来普及知识点(最为枯燥的部分,但忍下去)。
莫比乌斯函数
-
:莫比乌斯函数是啥啊?
:(莫比乌斯是一个由容斥系数所构成的函数)。
-
μ(d)的定义:
- 当d=1时,μ(d)=1;
- 当d= Π i = 1 k p i \Pi_{i=1}^kp_i Πi=1kpi且 p i p_i pi为互质素数时,μ(d)= ( − 1 ) k (-1)^k (−1)k。(说直白点,就是d分解质因数后,没有幂次大于平方的质因子,此时函数值根据分解的个数决定);
- 只要当d含有任何质因子的幂大于等于2,则函数值为0。
-
莫比乌斯函数的一些其他有趣的性质:
- 对于任意正整数n, ∑ d ∣ n μ ( d ) \sum_{d|n}μ(d) ∑d∣nμ(d)=[n=1]。(PS:这一条性质是莫比乌斯反演中最常用的)
- 对于任意正整数n, ∑ d ∣ n μ ( d ) d \sum_{d|n} \frac{μ(d)}{d} ∑d∣ndμ(d)= φ ( n ) n \frac{φ(n)}{n} nφ(n)。(这个性质很奇妙,把欧拉函数与莫比乌斯函数结合起来,下次学杜教筛会证明)。
-
求莫比乌斯函数的程序实现不难,只需要在线性筛的程序上略作修改,便可以筛出μ函数。
void mu_get(){
mu[1]=1;
for(int i=2;i<=n;++i){
if(!v[i]){
p[++m]=i,mu[i]=-1;
}
for(int j=1;j<=m;++j){
if(p[j]>v[i]||i*p[j]>n) break;
v[i*p[j]]=p[j];
if(i%p[j]) break;
else mu[i*p[j]]=-mu[i];
}
}
}
莫比乌斯反演
-
解决了莫比乌斯函数的问题后,我们就迎来了恶心的莫比乌斯反演。
-
莫比乌斯反演定理:F(n)和f(n)是定义在非负整数集合上的两个函数,并且满足条件:
F ( n ) = ∑ d ∣ n f ( d ) F(n)=\sum_{d|n}f(d) F(n)=d∣n∑f(d)
那么现在存在一个结论:
f ( n ) = ∑ d ∣ n μ ( d ) F ( n d ) f(n)=\sum_{d|n}μ(d)F(\frac{n}{d}) f(n)=d∣n∑μ(d)F(dn)
-
莫比乌斯反演的证明主要有两种方式,其中一种就是通过定义来证明;另一种利用狄利克雷卷积。先来说一说第一种证明方法:
∑ d ∣ n μ ( d ) F ( n d ) = ∑ d ∣ n μ ( d ) ∑ i ∣ n d f ( i ) \sum_{d|n}μ(d)F(\frac{n}{d})=\sum_{d|n}μ(d)\sum_{i|\frac{n}{d}}f(i) d∣n∑μ(d)F(dn)=d∣n∑μ(d)i∣dn∑f(i)
= ∑ i ∣ n f ( i ) ∑ d ∣ n i μ ( d ) = f ( n ) =\sum_{i|n}f(i)\sum_{d|\frac{n}{i}}μ(d)=f(n) =i∣n∑f(i)d∣in∑μ(d)=f(n)
(PS:如果不知道第二部如何推导第三部,可以考虑第二个式子枚举的i与d的关系,发现可以调换一下枚举的方式。如果不知道最后一步怎么来的,可以去看一下上面莫比乌斯函数的性质1)。
-
当然,莫比乌斯反演有另外的一种形式,当F(n)和f(n)满足:
F ( n ) = ∑ n ∣ d f ( d ) F(n)=\sum_{n|d}f(d) F(n)=n∣d∑f(d)
可以推出:
f ( n ) = ∑ n ∣ d μ ( d n ) F ( d ) f(n)=\sum_{n|d}μ(\frac{d}{n})F(d) f(n)=n∣d∑μ(nd)F(d)
-
感觉这个式子,可能在莫比乌斯反演中更加好用。
回到本题
-
显然,Ans= ∑ p ϵ p r i m ∑ i = 1 n ∑ j = 1 m [ g c d ( i , j ) = p ] \sum_{p\epsilon prim}\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=p] ∑pϵprim∑i=1n∑j=1m[gcd(i,j)=p]
-
对于这种和gcd有关的莫比乌斯反演,我们一般另f(d)为gcd(i,j)=d的个数,即f(d)= ∑ i = 1 n ∑ j = 1 m [ g c d ( i , j ) = d ] \sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=d] ∑i=1n∑j=1m[gcd(i,j)=d] 。那么F(n)为gcd(i,j)为n或者n的倍数的个数,即:
F ( n ) = ∑ n ∣ d f ( d ) = ⌊ N n ⌋ ⌊ M m ⌋ F(n)=\sum_{n|d}f(d)=\lfloor\frac{N}{n}\rfloor\lfloor\frac{M}{m}\rfloor F(n)=n∣d∑f(d)=⌊nN⌋⌊mM⌋
那么根据莫比乌斯反演定理:
f ( n ) = ∑ n ∣ d μ ( d n ) F ( d ) f(n)=\sum_{n|d}μ(\frac{d}{n})F(d) f(n)=n∣d∑μ(nd)F(d)
-
接下来就是化简式子了!
A n s = ∑ p ϵ p r i m f ( p ) Ans=\sum_{p\epsilon prim}f(p) Ans=pϵprim∑f(p)
= ∑ p ϵ p r i m ∑ p ∣ d μ ( d p ) F ( d ) =\sum_{p\epsilon prim}\sum_{p|d}μ(\frac{d}{p})F(d) =pϵprim∑p∣d∑μ(pd)F(d)
换一个枚举项,枚举 ⌊ d p ⌋ \lfloor\frac{d}{p}\rfloor ⌊pd⌋
∑ p ϵ p r i m ∑ d = 1 m i n ( ⌊ n p ⌋ , ⌊ m p ⌋ ) μ ( d ) F ( d p ) = ∑ p ϵ p r i m ∑ d = 1 m i n ( ⌊ n p ⌋ , ⌊ m p ⌋ ) μ ( d ) ⌊ n d p ⌋ ⌊ m d p ⌋ \sum_{p\epsilon prim}\sum_{d=1}^{min(\lfloor\frac{n}{p}\rfloor,\lfloor\frac{m}{p}\rfloor)}μ(d)F(dp)=\sum_{p\epsilon prim}\sum_{d=1}^{min(\lfloor\frac{n}{p}\rfloor,\lfloor\frac{m}{p}\rfloor)}μ(d)\lfloor\frac{n}{dp}\rfloor\lfloor\frac{m}{dp}\rfloor pϵprim∑d=1∑min(⌊pn⌋,⌊pm⌋)μ(d)F(dp)=pϵprim∑d=1∑min(⌊pn⌋,⌊pm⌋)μ(d)⌊dpn⌋⌊dpm⌋
这个dp看着很不爽,我们把它换成T
A n s = ∑ T = 1 m i n ( n , m ) ∑ t ∣ T , t ϵ p r i m μ ( T t ) ⌊ n T ⌋ ⌊ m T ⌋ Ans=\sum_{T=1}^{min(n,m)}\sum_{t|T,t\epsilon prim}μ(\frac{T}{t})\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor Ans=T=1∑min(n,m)t∣T,tϵprim∑μ(tT)⌊Tn⌋⌊Tm⌋
A n s = ∑ T = 1 m i n ( n , m ) ⌊ n T ⌋ ⌊ m T ⌋ ∑ t ∣ T , t ϵ p r i m μ ( T t ) Ans=\sum_{T=1}^{min(n,m)}\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\sum_{t|T,t\epsilon prim}μ(\frac{T}{t}) Ans=T=1∑min(n,m)⌊Tn⌋⌊Tm⌋t∣T,tϵprim∑μ(tT)
-
这样就已经可以O(n)求了。不过这道题有多组数据,所以直接O(n)求会挂。所以我们利用前缀和的思路,打一个整除分块,就可以O(n)预处理,O( n \sqrt{n} n)求每一组数据了。
历经磨难终于A掉了这道题,哎数学题推式子我总是看着题解一遍一遍推,还是没有感觉,弱啊……
Coding
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e7+10;
int n,m,cnt,p[N],v[N],mu[N];ll sum[N],ans,g[N];
void get_mu(int a){
mu[1]=1;
for(int i=2;i<=a;++i){
if(!v[i]){
p[++cnt]=i;mu[i]=-1;
}
for(int j=1;j<=cnt;++j){
if(p[j]*i>a) break;
v[i*p[j]]=p[j];
if(i%p[j]==0) break;
else mu[i*p[j]]=-mu[i];
}
}
for(int i=1;i<=cnt;++i){
for(int j=1;j*p[i]<=a;++j) g[p[i]*j]+=mu[j];
}
for(int i=1;i<=a;++i) sum[i]=sum[i-1]+g[i];
}
int main(){
get_mu(1e7);
int t;scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
ans=0;
if(n>m) swap(n,m);
for(int l=1,r;l<=n;l=r+1){
r=min((n/(n/l)),m/(m/l));
ans+=(sum[r]-sum[l-1])*(n/l)*(m/l);
}
printf("%lld\n",ans);
}
return 0;
}