莫比乌斯反演学习笔记

要说的话

有很多想说的,但不知从何说起。好久没有学数学了,整一个来记这些容易忘的东西。

Pre:整除分块

计算:

\[\sum\limits_{i=1}^{n}\left\lfloor\dfrac{n}{i}\right\rfloor \]

有很多题目会用到这个东西,发现\(\left\lfloor\dfrac{n}{i}\right\rfloor\)的分布呈块状,块的右端点为\(n/(n/i)\)

设当前块的左端点为\(l\) ,右端点为\(r\) ,那这一块的贡献是\(\left\lfloor\dfrac{n}{i}\right\rfloor*(r-l+1)\) ,这样就可以在\(O(\sqrt n)\)的时间内处理答案了!

𝜇

定义

首先是定义:

\(𝜇(1)=1\)

\(n=p_1^{c_1}*p_2^{c_2}*...*p_k^{c_k}\) (p为质数)

\(𝜇=(-1)^k\)

两个性质

\[(\sum\limits_{d|n}𝜇(d))=[n==1] \]

\[(\sum\limits_{d|n}\frac{𝜇(d)}{d})=\frac{\phi(n)}{n} \]

第一个用得比较多。

莫比乌斯反演

式子式子式子

由:

\[F(n)=\sum\limits_{d|n}f(d) \]

得:

\[f(n)=\sum\limits_{d|n}\mu(d)F(\frac{n}{d}) \]

我还以为反演是什么极端高级的东西,只是...一个东西的反表示...

这个是莫比乌斯反演定理

还有一个:

由:

\[F(n)=\sum\limits_{n|d}f(d) \]

得:

\[f(n)=\sum\limits_{n|d}\mu(\frac{d}{n})F(d) \]

好啦,基本的都讲完了。

一点经验

贯穿始终

把枚举约数-->转换成-->枚举约数和这个约数出现的次数

直接去掉不好枚举的\(i,j\) ,式子整理必用!!

TLE

能放到主函数的就尽量放,调用的函数越多,你的程序就越慢。

这一点会卡10分我有一题直接卡70

还有\(int\)\(long\) \(long\)的玄学时间

最重要的:尽量少用除法,可以用中间变量存一下结果。

一些题目

[POI2007 ZAP-Queries]

Link

Sol

我这个傻子先做不好做的才来做的这个

式子很好推,代码很好写。

\[\sum\limits_{i=1}^a\sum\limits_{j=1}^b[gcd(i,j)==d] \]

\[\sum\limits_{i=1}^{\left\lfloor\frac{a}{d}\right\rfloor}\sum\limits_{j=1}^{\left\lfloor\frac{b}{d}\right\rfloor}gcd(i,j)==1 \]

\[\begin{aligned}\sum\limits_{d=1}^n\sum\limits_{i=1}^{\left\lfloor\frac{a}{d}\right\rfloor}\sum\limits_{j=1}^{\left\lfloor\frac{b}{d}\right\rfloor}\sum\limits_{k|i,k|j}\mu(k)\end{aligned} \]

\[\sum\limits_{k=1}^n\mu(k)\left\lfloor\frac{a}{dk}\right\rfloor\left\lfloor\frac{b}{dk}\right\rfloor \]

好了。

Code

#include<bits/stdc++.h>
#define ll long long
#define V (50000)
#define N (50010)
using namespace std;
int T,n,m,d,tot,used[N],mu[N],p[N];
ll sum[N];
inline int read(){
	int w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
int main(){
	mu[1]=1;
	for(int i=2;i<=V;i++){
		if(!used[i]) p[++tot]=i,mu[i]=-1;
		for(int j=1;j<=tot&&i*p[j]<=V;j++){
			used[i*p[j]]=1;
			if(i%p[j]) mu[i*p[j]]=-mu[i];
			else break;
		}
	}
	for(int i=1;i<=V;i++) sum[i]=sum[i-1]+mu[i];
	T=read();
	while(T--){
		n=read(),m=read(),d=read();
		if(n>m) swap(n,m);
		n/=d,m/=d;
		ll res=0;
		for(ll l=1,r;l<=n;l=r+1){
			int x=n/l,y=m/l;
			r=min(n/x,m/y);
			res+=(sum[r]-sum[l-1])*x*y;
		}
		printf("%lld\n",res);
	}
	return 0;
}

捡经验

[[HAOI2011] Problem b]

Link

Sol

一个简单的变式,想象有一个存答案的表,类比一下二维前缀和。

Code

#include<bits/stdc++.h>
#define ll long long
#define V (50000)
#define N (50010)
using namespace std;
int T,n1,n2,n3,n4,d,tot,used[N],mu[N],p[N],sum[N];
inline int read(){
	int w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
inline int solo(int n,int m){
	int res=0;
	for(int l=1,r;l<=min(n,m);l=r+1){
		int x=n/l,y=m/l;
		r=min(n/x,m/y);
		res+=(sum[r]-sum[l-1])*x*y;
	}
	return res;
}
int main(){
	mu[1]=1,sum[1]=1;
	for(int i=2;i<=V;i++){
		if(!used[i]) p[++tot]=i,mu[i]=-1;
		for(int j=1;j<=tot&&i*p[j]<=V;j++){
			used[i*p[j]]=1;
			if(i%p[j]) mu[i*p[j]]=-mu[i];
			else break;
		}
		sum[i]=sum[i-1]+mu[i];
	}
	T=read();
	while(T--){
		n1=read(),n2=read(),n3=read(),n4=read(),d=read();
//		cout<<ans1<<" "<<ans2<<" "<<ans3<<" "<<ans4<<endl;
		printf("%d\n",solo(n2/d,n4/d)+solo((n1-1)/d,(n3-1)/d)-solo(n2/d,(n3-1)/d)-solo((n1-1)/d,n4/d));
	}
	return 0;
}

[SDOI2014]数表

Link

求:

\[\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}σ(gcd(i,j))[σ(gcd(i,j))<=k] \]

Sol

完整的过程就不给了,我有几步没推好的放一下。

莫比乌斯反演后:

\[\sum\limits_{d=1}^{n}σ(d)\sum\limits_{i=1}^{\frac{n}{d}}\sum\limits_{j=1}^{\frac{m}{d}}\sum\limits_{x|gcd(i,j)}𝜇(x) \]

(除法默认下取整)

𝜇提到前面来:

\(i \in [1,\frac{n}{d}],j \in [1,\frac{m}{d}]\)中,有\(\frac{n}{dx}*\frac{m}{dx}\)\(𝜇(x)\)

得:

\[\sum\limits_{d=1}^{n}σ(d)\sum\limits_{x=1}^{\left\lfloor\dfrac{n}{d}\right\rfloor} \mu(x)\left\lfloor\dfrac{n}{dx}\right\rfloor\left\lfloor\dfrac{m}{dx}\right\rfloor \]

\(T=dx\)得:

\[\sum\limits_{T=1}^{n}\left\lfloor\dfrac{n}{T}\right\rfloor\left\lfloor\dfrac{m}{T}\right\rfloor\sum\limits_{d|T}σ(d)\mu(\frac{T}{d}) \]

Code

#include<bits/stdc++.h>
#define lb(i) (i&(-i))
#define ll long long
#define V (100000)
#define N (100010)
#define P (1<<31)
using namespace std;
struct xbk{int n,m,k,id;}q[N];
struct xll{ll v,id;}d[N];
int T,tot,p[N],u[N];
ll c[N],dd[N],ans[N];
bool used[N];
inline int read(){
	int w=0;
	char ch=getchar();
	bool f=0;
	while(ch>'9'||ch<'0'){
		if(ch=='-') f=1;
		ch=getchar();	
	} 
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return f?-w:w;
}
inline bool cmp1(xll a,xll b){return a.v<b.v;}
inline bool cmp2(xbk a,xbk b){return a.k<b.k;}
inline void oula(){
	u[1]=1;
	for(int i=2;i<=V;i++){
		if(!used[i]) u[i]=-1,p[++tot]=i;
		for(int j=1;j<=tot;j++){
			if(i*p[j]>V) break;
		    used[i*p[j]]=1;
		    if(!(i%p[j])) break;
			u[i*p[j]]=-u[i];
		}
	}
	for(int i=1;i<=V;i++)
		for(int j=i;j<=V;j+=i) dd[j]+=i;
  //dd是σ
	for(int i=1;i<=V;i++) d[i].id=i,d[i].v=dd[i];
	sort(d+1,d+1+V,cmp1);
	return;
}
inline ll ask(int pos){
	ll res=0;
	for(int i=pos;i;i-=lb(i)) res+=c[i];
	return res;
}
inline void add(int pos,ll v){
	for(int i=pos;i<=V;i+=lb(i)) c[i]+=v;	
}
//树状数组维护𝜇σ
inline void add1(int x){
	for(ll i=1;i*x<=V;i++) add(i*x,u[i]*dd[x]);
}
inline ll solo(ll nn,ll mm){
	ll res=0;
	if(nn>mm) swap(nn,mm);
	for(int l=1,r;l<=nn;l=r+1){
		r=min(nn/(nn/l),mm/(mm/l));
		res+=(nn/l)*(mm/l)*(ask(r)-ask(l-1));
	}
	return res%P;
}
int main(){
	oula();
	T=read();
	for(int i=1;i<=T;i++)
		q[i].n=read(),q[i].m=read(),q[i].k=read(),q[i].id=i;
	sort(q+1,q+1+T,cmp2);
	int now=0;
	for(int i=1;i<=T;i++){
		while(d[now+1].v<=q[i].k&&now<V) add1(d[++now].id);
		ans[q[i].id]=solo(q[i].n,q[i].m);
	}
	for(int i=1;i<=T;i++) printf("%lld\n",ans[i]);
	return 0;
}

[LuoguP2257 YY的GCD]

Link

求:

\[\sum\limits_{i=1}^n\sum\limits_{j=1}^m[gcd(i,j)==prime]​ \]

Sol

直接推(pm指质数集)。

\[\sum\limits_{d=1,d\in pm}\sum\limits_{i=1}^{\left\lfloor\dfrac{n}{d}\right\rfloor}\sum\limits_{j=1}^{\left\lfloor\dfrac{m}{d}\right\rfloor}[gcd(i,j)=1] \]

\[\sum\limits_{d=1,d\in pm}\sum\limits_{i=1}^{\left\lfloor\dfrac{n}{d}\right\rfloor}\sum\limits_{j=1}^{\left\lfloor\dfrac{m}{d}\right\rfloor}\sum\limits_{k|i,k|j}\mu(k) \]

经典操作:

\[\sum\limits_{d=1,d\in pm}\sum\limits_{k=1}^n\left\lfloor\dfrac{n}{dk}\right\rfloor\left\lfloor\dfrac{m}{dk}\right\rfloor\mu(k) \]

\(T=dk\)

\[\sum\limits_{T=1}^n\left\lfloor\dfrac{n}{T}\right\rfloor\left\lfloor\dfrac{m}{T}\right\rfloor\sum\limits_{d=1,d|T}\mu(\frac{T}{d}) \]

前缀和维护\(\mu\) ,整除分块维护前面的就好啦。

Code

#include<bits/stdc++.h>
#define ll long long
#define V (10000000)
#define N (10000010)
using namespace std;
int T,n,m,tot,used[N],mu[N],p[N];
ll sum[N];
inline int read(){
	int w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
int main(){
	mu[1]=1;
	for(int i=2;i<=V;i++){
		if(!used[i]) p[++tot]=i,mu[i]=-1;
		for(int j=1;j<=tot&&i*p[j]<=V;j++){
			used[i*p[j]]=1;
			if(i%p[j]) mu[i*p[j]]=-mu[i];
			else break;
		}
	}
	for(int i=1;i<=tot;i++)
		for(int j=p[i];j<=V;j+=p[i]) sum[j]+=mu[j/p[i]];
	for(int i=2;i<=V;i++) sum[i]+=sum[i-1];
	T=read();
	while(T--){
		n=read(),m=read();
		if(n>m) swap(n,m);
		ll res=0;
		for(ll l=1,r;l<=n;l=r+1){
			int x=n/l,y=m/l;
			r=min(n/x,m/y);
			res+=(sum[r]-sum[l-1])*x*y;
		}
		printf("%lld\n",res);
	}
	return 0;
}

捡经验

[LuoguP2398 GCD SUM]

Link

Sol

这道题看着简单,甚至只有蓝题难度 ,但我觉得确实很有水平。正是:

所以这道题就是个烂题,但它还是非常的好。

By 神仙学长gzy

推式子:

\[\sum\limits_{i=1}^n\sum\limits_{j=1}^ngcd(i,j) \]

套路枚举\(gcd\)

\[\sum\limits_{d=1}^nd\sum\limits_{i=1}^\left\lfloor\dfrac{n}{d}\right\rfloor\sum\limits_{j=1}^{\left\lfloor\dfrac{n}{d}\right\rfloor}[gcd(i,j)=1] \]

\[\sum\limits_{d=1}^nd\sum\limits_{i=1}^\left\lfloor\dfrac{n}{d}\right\rfloor\sum\limits_{j=1}^{\left\lfloor\dfrac{n}{d}\right\rfloor}\sum\limits_{k|i,k|j}\mu(k) \]

\[\sum\limits_{d=1}^nd\sum\limits_{k=1}^\left\lfloor\dfrac{n}{d}\right\rfloor \mu(k)\left\lfloor\dfrac{n}{dk}\right\rfloor^2 \]

\(T=dk\)

\[\sum\limits_{T=1}^n\left\lfloor\dfrac{n}{T}\right\rfloor^2\sum\limits_{d=1,d|T}^nid(d)\mu(\frac{T}{d}) \]

后面那一块为\(\phi(T)\)

为什么?狄利克雷卷积告诉我们的。

然后就可以整除分块了。

Code

#include<bits/stdc++.h>
#define V (100000)
#define N (100010)
#define ll long long
using namespace std;
int n,tot;
ll ans,phi[N],p[N];
bool used[N];
inline int read(){
	int w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
inline void oula(){
	phi[1]=1;
	for(int i=2;i<=V;i++){
		if(!used[i]) p[++tot]=i,phi[i]=i-1;
		for(int j=1;j<=tot&&p[j]*i<=V;j++){
			used[i*p[j]]=1;
			if(i%p[j]) phi[i*p[j]]=phi[i]*phi[p[j]];
			else{
				phi[i*p[j]]=phi[i]*p[j];
				break;
			}
		}
	}
	for(int i=1;i<=V;i++) phi[i]+=phi[i-1];
	return;
}
int main(){
	n=read();
	oula();
	for(int l=1,r;l<=n;l=r+1){
		ll now=n/l;
		r=n/now;
		ans+=now*now*(phi[r]-phi[l-1]);
	}
	printf("%lld\n",ans);
	return 0;
}

[Luogu P5521 Product]

Link

Sol

累乘转指数挺巧妙的。

\[\prod\limits_{i=1}^n\prod\limits_{j=1}^n\frac{lcm(i,j)}{gcd(i,j)} \]

\[\prod\limits_{i=1}^n\prod\limits_{j=1}^n\frac{ij}{gcd(i,j)^2} \]

\[\prod\limits_{i=1}^n\prod\limits_{j=1}^nij\prod\limits_{i=1}^n\prod\limits_{j=1}^ngcd(i,j)^{-2} \]

设左边的为\(ans_1\) ,右边的为\(ans_2\)

\[ans_1=(n!)^{2n} \]

\[ans_2=\prod\limits_{d=1}^nd^{\sum\limits_{i=1}^n\sum\limits_{j=1}^n[gcd(i,j)==d]} \]

\[ans_2=\prod\limits_{d=1}^nd^{\sum\limits_{i=1}^{\frac{n}{d}}\sum\limits_{j=1}^{\frac{n}{d}}[gcd(i,j)==1]} \]

\[ans_2=\prod\limits_{d=1}^nd^{\sum\limits_{i=1}^{\frac{n}{d}}\phi(i)} \]

好啦。

Code

#include<bits/stdc++.h>
#define N (1000010)
#define V (1000000)
using namespace std;
const int P=104857601;
int n,p[500010],phi[N];
bool used[N];
inline int ksm(int a,int b){
	int res=1;
	while(b){
		if(b&1) res=1ll*res*a%P;
		a=1ll*a*a%P;
		b>>=1;
	}
	return res;
}
int main(){
	scanf("%d",&n);
	phi[1]=1;
	for(int i=2;i<=V;i++){
		if(!used[i]) p[++p[0]]=i,phi[i]=i-1;
		for(int j=1;j<=p[0]&&i*p[j]<=V;j++){
			used[i*p[j]]=1;
			if(i%p[j]) phi[i*p[j]]=phi[i]*phi[p[j]];
			else{
				phi[i*p[j]]=phi[i]*p[j];
				break;
			}
		}
	}
	int res0=1,res1=1;
	for(int i=1;i<=V;i++) phi[i]=((phi[i]<<1)%(P-1)+phi[i-1])%(P-1);
	for(int i=2;i<=n;i++) res0=1ll*res0*i%P;
	res0=ksm(res0,n+n);
	for(int i=2;i<=n;i++) res1=1ll*res1*ksm(i,phi[n/i]-1)%P;
	res1=ksm(res1,P-2);
	int res=1ll*res0*res1%P*res1%P;
	printf("%d\n",res);
	return 0;
}

未完待续❀

posted @ 2021-06-01 19:30  xxbbkk  阅读(78)  评论(1编辑  收藏  举报