YY的gcd[莫比乌斯反演模板题]

版权声明:这是gigo写的QAQ https://blog.csdn.net/qq_42835815/article/details/84996065

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;
} 

猜你喜欢

转载自blog.csdn.net/qq_42835815/article/details/84996065
今日推荐