1. 程式人生 > >YY的gcd[莫比烏斯反演模板題]

YY的gcd[莫比烏斯反演模板題]

YY的gcd啊,,題意如下;

多組資料

每組資料給出n,m,k;

求1<=i<=n,1<=j<=m,gcd(i,j)=k;的對數。

資料數最高為10000,n,m<=10000000

毒瘤資料。

根據上文所說,莫比烏斯反演最重要的第一步是找準f 和F兩個函式。

明顯本題f(d)指滿足gcd(i,j)的對數。

那F的含義就如上文所示了。

所以

f(d)=\sum_{i=1}^{n}\sum_{j=1}^m(gcd(i,j)=d)

F(n)=\sum_{n|d}f(d)

Ans=\sum_{p\in prim}\sum_{i=1}^n\sum_{j=1}^m(gcd(i,j)=p)

把f(p)代入得

Ans=\sum_{p\in prim}f(p)

反演一下得

Ans=\sum_{p\in prim}\sum_{p|d}\mu(\left \lfloor\frac{d}{p} \right \rfloor)F(d)

可以看出這裡我們在列舉質數,那我們換一下,列舉\left \lfloor \frac{d}{p} \right \rfloor,並將F代入後半截得

Ans=\sum_{p\in prim}\sum_{d=1}^{min(\left \lfloor\frac{n}{p} \right \rfloor,\left \lfloor \frac{m}{p} \right \rfloor)}\mu(d)F(dp)=\sum_{p\in prim}\sum_{d=1}^{min(\left \lfloor \frac{n}{p}\right \rfloor,\left \lfloor \frac{m}{p} \right \rfloor)}\mu(d)\left \lfloor \frac{n}{dp}\right \rfloor\left \lfloor\frac{m}{dp} \right \rfloor

為了方便表示,我們設T=dp

Ans=\sum_{T=1}^{min(n,m)}\sum_{t|T,t\in prim}\mu(\left \lfloor \frac{T}{t} \right \rfloor)\left \lfloor \frac{n}{T} \right \rfloor\left \lfloor \frac{m}{T} \right \rfloor

所以到現在為止,這個式子的複雜度已經是O(n)了。

但是由於是多組資料,所以我們要把複雜度降成O(根號下n);觀察式子發現很多向下取整得到的值是一樣的(在T不一樣的時候),所以我們運用整除分塊。整除分塊後一坨一坨處理就需要一個字首和sum。

整除分塊很簡單,,基本可以背程式碼,,大家請看程式碼就可以了。

#include<bits/stdc++.h>
#define in read()
using namespace std;
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();
		if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar(); 
	}
	return cnt*f;
}
int mul[10000003];
int prim[10000003],vis[10000003],cnt;
long long g[10000003],sum[10000003];//g與sum見mull處理區,自行理解ovo 
int t;
void mull(int n){
	mul[1]=1;
	for(int i=2;i<=n;i++){
		if(!vis[i]){
			prim[++cnt]=i;mul[i]=-1;
		}
		for(int j=1;j<=cnt&&prim[j]*i<=n;j++){
			vis[prim[j]*i]=1;
			if(i%prim[j]==0)break;
			mul[prim[j]*i]=-mul[i];
		}
	}//篩完了mu 
	for(int j=1;j<=cnt;j++){
		for(int i=1;i*prim[j]<=n;i++){
			g[prim[j]*i]+=mul[i];//g[i]存最終式子的最後面那一坨 
		}
	}
	for(int i=1;i<=n;i++)sum[i]=sum[i-1]+g[i];//g的字首和 
}
int main(){
	t=in;int n,m;
	mull(10000000);
	while(t--){
		n=in;m=in;
		if(n>m)swap(n,m);
		long long ans=0;
		for(register int l=1,r;l<=n;l=r+1){
			r=min(n/(n/l),m/(m/l));//注意,這裡是整除分塊。經典的n/(n/l)保證分出來的複雜度最接近根號n 
			ans+=1ll*(n/l)*(m/l)*(sum[r]-sum[l-1]);//字首和的運用。 
		}
		printf("%lld\n",ans);
	}
	return 0;
}