【数论】莫比乌斯反演(欧拉反演)进阶-杜教筛

前言

这里需要对莫反有一些基础
不会的可以点这里

回忆

  • f ( n ) = ∑ d ∣ n g ( d ) → g ( n ) = ∑ d ∣ n f ( d ) μ ( n d ) f(n)=\sum_{d|n}g(d)\rightarrow g(n)=\sum_{d|n}f(d)\mu(\frac{n}{d}) f(n)=dng(d)g(n)=dnf(d)μ(dn)
  • ∑ d ∣ n μ ( d ) = [ n = 1 ] \sum_{d|n}\mu(d)=[n=1] dnμ(d)=[n=1]
  • ∑ i = 1 n ⌊ n i ⌋ = \sum_{i=1}^n\left\lfloor\frac{n}{i}\right\rfloor= i=1nin= 你应该知道怎么求
  • ∑ i = 1 1 0 9 μ ( i ) = \sum_{i=1}^{10^9}\mu(i)= i=1109μ(i)= 你可能需要知道怎么求
  • 线性筛 μ ( i ) , φ ( i ) \mu(i),\varphi(i) μ(i),φ(i)
  • 一些数学能力

题集

1

∏ i = 1 n ∏ j = 1 m gcd ⁡ ( i , j ) \large\prod_{i=1}^n\prod_{j=1}^m\gcd(i,j) i=1nj=1mgcd(i,j)
= ∏ d = 1 d ∑ i = 1 ∑ j = 1 m [ gcd ⁡ ( i , j ) = d ] =\prod_{d=1}d^{\sum_{i=1}\sum_{j=1}^m[\gcd(i,j)=d]} =d=1di=1j=1m[gcd(i,j)=d]
= ∏ d = 1 d ∑ k = 1 min ⁡ ( n , m ) d μ ( k ) n k d m k d =\prod_{d=1}d^{\sum_{k=1}^\frac{\min(n,m)}{d}\mu(k)\frac{n}{kd}\frac{m}{kd}} =d=1dk=1dmin(n,m)μ(k)kdnkdm
= ∏ T = 1 ( ∏ k ∣ T ( T k ) μ ( k ) ) n T m T =\prod_{T=1}(\prod_{k|T}(\frac{T}{k})^{\mu(k)})^{\frac{n}{T}\frac{m}{T}} =T=1(kT(kT)μ(k))TnTm
f ( T ) = ∏ k ∣ T ( T k ) μ ( k ) f(T)=\prod_{k|T}(\frac{T}{k})^{\mu(k)} f(T)=kT(kT)μ(k)
线性筛+整出分块即可
Code:

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)
#define cou(i) cout<<fixed<<setprecision(i)
using namespace std;
const int N=1e7+1,mod=1e9+7;
int t,n,m,k,ans,res;
unordered_map<int,int>Mu;
struct fy{
	int prv[N],cnt,mu[N],F[N];
	bool pr[N];
	int qmi(int x,int y){
		int res=1;
		while(y>0){
			if(y&1)
				res=res*x%mod;
			x=x*x%mod,y>>=1;
		}
		return res;
	}
	void ola(int x){
		pr[1]=mu[1]=F[1]=1;
		for(int i=2;i<=x;i++){
			if(!pr[i])
				prv[++cnt]=i,mu[i]=-1,F[i]=i;
			for(int j=1;j<=cnt&&i*prv[j]<=x;j++){
				int u=i*prv[j];
				pr[u]=1;
				if(i%prv[j]==0){
					mu[u]=0;
					F[u]=F[i];
					break;
				}
				else{
					mu[u]=-mu[i];
					F[u]=1;
				}
			}
		}
	}
	void getsum(int x){
		F[0]=1;
		for(int i=1;i<=x;i++){
			F[i]*=F[i-1],F[i]%=mod; 
		}
	}
	int summu(int x){
		int res=1;
		if(x<N)
			return mu[x];
		if(Mu[x])
			return Mu[x];
		for(int l=2,r;l<=x;l=r+1){
			r=x/(x/l);
			res-=(summu(x/l))*(r-l+1);
		}
		Mu[x]=res;
		return res;
	}
	int sumphi(int x){
		int res=0;
		for(int l=1,r;l<=x;l=r+1){
			r=x/(x/l);
			res+=(summu(r)-summu(l-1))*(x/l)*(x/l);
		}
		return res;
	}
}A;
signed main(){
	IOS;
	A.ola(N-1);
	A.getsum(N-1);
	cin>>t;
	while(t--){
		cin>>n>>m;
		int ans=1ll;
		for(int l=1,r;l<=min(n,m);l=r+1){
			r=min(n/(n/l),m/(m/l));
			int res=A.F[r]*A.qmi(A.F[l-1],mod-2)%mod;
			ans=ans*A.qmi(res,(n/l)*(m/l))%mod;
		}
		cout<<ans<<"\n";
	}
	return 0;
}

经验:
1
2
3

2

Link
在这里插入图片描述
暴力推式子。
关键:
gcd ⁡ ( i j , j k , k i ) = gcd ⁡ ( i , j ) gcd ⁡ ( j , k ) gcd ⁡ ( k , i ) gcd ⁡ ( i , j , k ) \large{\gcd(ij,jk,ki)=\frac{\gcd(i,j)\gcd(j,k)\gcd(k,i)}{\gcd(i,j,k)}} gcd(ij,jk,ki)=gcd(i,j,k)gcd(i,j)gcd(j,k)gcd(k,i)
然后可得原式=于神之怒加强版这里
好像这黑题有那么一点点水啊
Code:

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)
using namespace std;
const int N=2e7+1,mod=1e9+7;
int t,n,m,p,k;
struct fy{
	int prv[N],cnt,g[N],s[N];
	bool pr[N];
	inline int qmi(int x,int y){
		int res=1;
		while(y>0){
			if(y&1)
				res=res*x%mod;
			x=x*x%mod,y>>=1;
		}
		return res;
	}
	void ola(int x){
		pr[1]=g[1]=1;
		for(int i=2;i<=x;i++){
			if(!pr[i])
				prv[++cnt]=i,g[i]=(qmi(i,k)-1+mod)%mod;
			for(int j=1;j<=cnt&&i*prv[j]<=x;j++){
				int u=i*prv[j];
				pr[u]=1;
				if(i%prv[j]==0){
					g[u]=g[i]*qmi(prv[j],k)%mod;
					break;
				}
				else{
					g[u]=g[i]*g[prv[j]]%mod;
				}
			}
		}
	}
	void getsum(int x){
		for(int i=1;i<=x;i++)
			g[i]=(g[i]+g[i-1])%mod;
	}
}A;
signed main(){
	IOS;
	k=2;
	cin>>t;
	A.ola(N-1);
	A.getsum(N-1);
	while(t--){
		cin>>n>>m>>p;
		int ans=0,res=0;
		for(int l=1,r;l<=min(n,m);l=r+1){
			r=min(n/(n/l),m/(m/l));
			res+=(n/l)*(m/l)%mod*((A.g[r]-A.g[l-1]+mod)%mod)%mod;
			res%=mod;
		}
		ans+=res*p;
		ans%=mod;
		res=0;
		for(int l=1,r;l<=min(m,p);l=r+1){
			r=min(m/(m/l),p/(p/l));
			res+=(m/l)*(p/l)%mod*((A.g[r]-A.g[l-1]+mod)%mod)%mod;
			res%=mod;
		}
		ans+=res*n;
		ans%=mod;
		res=0;
		for(int l=1,r;l<=min(p,n);l=r+1){
			r=min(p/(p/l),n/(n/l));
			res+=(p/l)*(n/l)%mod*((A.g[r]-A.g[l-1]+mod)%mod)%mod;
			res%=mod;
		}
		ans+=res*m;
		ans%=mod;
		cout<<ans<<"\n";
	}
	return 0;
}

杜教筛

来自 OI-wiki的资料:

这里直达

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这里直达OI-wiki


例题

给定一个正整数,求
a n s 1 = ∑ i = 1 n φ ( i ) , a n s 2 = ∑ i = 1 n μ ( i ) ans_1=\sum_{i=1}^n\varphi(i),ans_2=\sum_{i=1}^n \mu(i) ans1=i=1nφ(i),ans2=i=1nμ(i)
输入的第一行为一个整数,表示数据组数 T T T
接下来 T T T 行,每行一个整数 n n n,表示一组询问。
对于每组询问,输出一行两个整数,分别代表 a n s 1 ans_1 ans1 a n s 2 ans_2 ans2
对于全部的测试点,保证 1 ≤ T ≤ 10 1 \leq T \leq 10 1T10 1 ≤ n < 2 31 1 \leq n \lt 2^{31} 1n<231
考虑使用杜教筛。
S 1 ( n ) = ∑ i = 1 n μ ( i ) S_1(n)=\sum_{i=1}^n\mu(i) S1(n)=i=1nμ(i)
在这里插入图片描述
在这里插入图片描述
那样我们就完成了两个最简单的例题了
Code:

#include<bits/stdc++.h>
#define int __int128
using namespace std;
const int N=2e6+1,mod=1e9+7;
int t,n,m,k;
void write(int x){
	if(x<0)
		putchar('-'),x=-x;
	if(x>=10)
		write((int)(x/10));
	char o='0'+x%10;
	putchar(o);
}
void read(int &x){
	x=0;
	int y=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-'){
			y=-1;
			break;
		}
		c=getchar();
	}
	while(c<='9'&&c>='0')
		x=x*10+c-'0',c=getchar();
	x*=y;
}
map<int,int>Mu,Phi;
struct fy{
	int prv[N],cnt,phi[N],mu[N];
	bool pr[N];
	void ola(int x){
		pr[1]=mu[1]=1;
		for(int i=2;i<=x;i++){
			if(!pr[i])
				prv[++cnt]=i,mu[i]=-1;
			for(int j=1;j<=cnt&&i*prv[j]<=x;j++){
				int u=i*prv[j];
				pr[u]=1;
				if(i%prv[j]==0){
					mu[u]=0;
					break;
				}
				else{
					mu[u]=-mu[i];
				}
			}
		}
	}
	void getsumF(int x){
		for(int i=1;i<=x;i++)
			mu[i]+=mu[i-1];
	}
	int summu(int x){
		int res=1;
		if(x<N)
			return mu[x];
		if(Mu[x])
			return Mu[x];
		for(int l=2,r;l<=x;l=r+1){
			r=x/(x/l);
			res-=(summu(x/l))*(r-l+1);
		}
		Mu[x]=res;
		return res;
	}
	int sumphi(int x){
		int res=0;
		for(int l=1,r;l<=x;l=r+1){
			r=x/(x/l);
			res+=(summu(r)-summu(l-1))*(n/l)*(n/l);
		}
		return res;
	}
}A;
signed main(){
	A.ola(N-1);
	A.getsumF(N-1);
	read(t);
	while(t--){
		read(n);
		write((A.sumphi(n)-1)/2+1);
		putchar(' ');
		write(A.summu(n));
		putchar('\n');
	}
	return 0;
}
//此代码有一点瑕疵,但确实可以过模板题
  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值