莫比乌斯反演

以下内容中,a|b表示ab的因数,a|b表示a不是b的因数

前置知识:积性函数相关

整除分块

狄利克雷卷积

定义:任意函数f(n),g(n)的狄利克雷卷积为(fg)(n)=d|nf(d)g(nd)

其中d|n是要枚举n的因数并累加

换一种写法就是xy=nf(x)g(y)

还可以用代码写成:

int dirichlet(int n) {
	int ans=0;
	for(int i=1; i<=n; ++i) {
		if(n%i==0) {
			ans+=f[i]*g[n/i];//f[i]为f(i),g[n/i]为g(n/i)
		}
	}
	return ans;
}

这个卷积满足:

  1. 交换律fg=gf
  2. 结合律fgh=f(gh)
  3. 分配率f(g+h)=fg+fh
  4. f(x),g(x)都为积性函数时,(fg)(x)为积性函数
  5. (fg)(x),g(x)都为积性函数时,f(x)为积性函数

常见的狄利克雷卷积有:

  1. φI=id
  2. μI=ϵ
  3. ϵf=f
  4. idI=σ
  5. II=d
  6. (λI)(n)=[nZ]

简单证明:(3,4,5见相关函数的定义:积性函数相关

  1. φI=id(下面以n=6为例)

[1,n]的整数都除以n

1÷6=16,5÷6=56,对应与6互质的数,个数为φ(6)

2÷6=13,4÷6=23,对应与3互质的数,个数为φ(3)

3÷6=12,对应与2互质的数,个数为φ(2)

6÷6=11,对应与1互质的数,个数为φ(1)

φ(1)+φ(2)+φ(3)+φ(6)=6d|6φ(d)=6

  1. μI=ϵ

n质因数分解后为i=1kpici

n>1时:

d|nμ(d)=μ(1)+μ(p1)+μ(p12)++μ(pici)++μ(p1c1p2c2pkck)=μ(1)+μ(p1)+μ(p12)++μ(pici)++μ(p1c1)μ(p2c2)μ(pkck)=i=1kj=0ciμ(pij)=i=1kj=01μ(pij)=i=1k(11)=0

n=1时,d|nμ(d)=μ(1)=1

  1. (λI)(n)=[nZ]

同样设n质因数分解后为i=1kpici

d|nλ(d)=λ(1)+λ(p1)+λ(p12)++λ(pici)++λ(p1c1p2c2pkck)=λ(1)+λ(p1)+λ(p12)++λ(pici)++λ(p1c1)λ(p2c2)λ(pkck)=i=1kj=0ciλ(pij)=i=1kj=0ci(1)j=i=1k[ci0(mod2)]

最后那个式子只有在ci都为偶数的时候才为1,所以n为完全平方数时才是1

一道题

给出n(n1012),求k=1ni|kj|iλ(i)λ(j)(mod998244353)

我们可以先枚举i,再枚举满足i|kk

i=1ni|kj|iλ(i)λ(j)

可以提出公因式:

i=1nλ(i)(i|k1)j|iλ(j)

中间的部分就是求小于等于ni的倍数有多少,也就是ni

i=1nλ(i)nij|iλ(j)

后面的部分是λI,上面说了,是[nZ]

i=1nλ(i)ni[iZ]

这样我们就只用考虑完全平方数了。

而且当i为完全平方数时λ(i)=1

i=1nni2

O(n)可以通过。

再来一题

P3455 [POI2007]ZAP-Queries

先把答案用式子表示一下:

i=1aj=1b[gcd(i,j)=d]

之后把i,j同时除以d

i=1adj=1bd[gcd(i,j)=1]

[gcd(i,j)=1]就是ϵ(gcd(i,j)),把ϵ=μ1代入上式得:

i=1adj=1bdk|gcd(i,j)μ(k)

把最后一个循环提前:

k=1adμ(k)i=1adj=1bd[k|gcd(i,j)]

k|gcd(i,j)其实就是k|i&k|j,代入上式

k=1adμ(k)i=1adj=1bd[k|i&k|j]

=k=1adμ(k)i=1adkj=1bdk

=k=1adμ(k)adkbdk

线性筛μ的前缀和并用整除分块就可以达到每次询问O(a),总复杂度O(Ta)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=50005;
typedef long long ll;
bool isp[MAXN];
ll sum[MAXN];
int prime[MAXN],pcnt,mu[MAXN];
ll f(int n,int m) {
    ll 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])*1ll*(n/l)*(m/l);
    }
    return ans;
}
int main() {
    sum[1]=mu[1]=1;
    for(int i=2; i<MAXN; ++i) {
        if(!isp[i]) {
            prime[++pcnt]=i;
            mu[i]=-1;
        }
        for(int j=1,x; j<=pcnt&&(x=i*prime[j])<MAXN; ++j) {
            isp[x]=true;
            if(i%prime[j]) {
                mu[x]=-mu[i];
            } else {
                mu[x]=0;
                break;
            }
        }
        sum[i]=sum[i-1]+mu[i];
    }
    int t,a,b,d;
    scanf("%d",&t);
    while(t--) {
        scanf("%d%d%d",&a,&b,&d);
        printf("%lld\n",f(a/d,b/d));
    }
    return 0;
}

莫比乌斯反演

第一种形式:对于fI=g,有f=gμ,反之亦然

证明:

两边都与μ做卷积

fIμ=gμ

结合律:

f(Iμ)=gμ

ϵ=1μ得:

fϵ=gμ

fϵ=f得:

f=gμ

第二种形式:n|df(d)=g(n),则f(n)=n|dg(d)μ(dn)

证明:

n|dg(d)μ(dn)

d除以n

=d=1+g(dn)μ(d)

g写成f

=d=1+μ(d)nd|tf(t)

枚举td即是tn的因数

=n|tf(t)d|tnμ(d)

后面的是μI的形式,改写成ϵ

=n|tf(t)[tn=1]=f(n)

来几道题:

三倍经验的题:
SP3871 GCDEX - GCD ExtremeUVA11426 拿行李(极限版) GCD - Extreme (II)UVA11424 GCD - Extreme (I)

我们换个思路,先写成这个形式:

i=1nj=1i1gcd(i,j)

根据P2303的结论i=1ngcd(i,n)=d|ndφ(nd)(上面有)把式子写成:

i=1n((j=1igcd(i,j))gcd(i,i))=i=1n(d|idφ(id))i=(i=1nd|idφ(id))n(n+1)2

g(i)=d|idφ(id)

我们可以试着O(n)线性筛出g(i),同时算个前缀和就预处理完了。

下面写的i,primej为欧拉筛代码中的i,prime[j]

我们设i分解质因数后是j=1kpjcjlowi=p1c1。其中p1是最小的质因子。

而且primeji×primej得最小质因子(欧拉筛的性质)

则当primej|i时,p1=primej

可以得到,g(1)=1,g(p)=2p1p是质数。

由于函数id(x)φ(x)是积性函数

那么g(i)=idφ也是积性函数

则当iprimej互质时,g(i×primej)=g(i)×g(primej),且lowi×primej=primej

iprimej不互质即primej|i时,有p1=primej(上面讲了),根据low的定义得

gcd(ilowi,p1×lowi)=1,lowi×primej=lowi×primej

这样就可以用积性函数的定义求g(i×primej)

g(i×primej)=g(ilowi)×g(p1×lowi)

但是当i=lowi时,这个式子就会变成g(i×primej)=g(1)×g(i×primej)=g(i×primej),无法递推

特殊处理一下i=lowi的情况:

i=lowii=p1c1,则i×primej=p1c1+1

g(p1c1+1)=d|p1c1+1dφ(p1c1+1d)

d=1的一项单独提出:

g(p1c1+1)=d|p1c1+1&d>1dφ(p1c1+1d)+φ(p1c1+1)

内的d此时只能是p1,p12,,p1c1+1,我们就可以把d除以p1

g(p1c1+1)=d|p1c1dp1φ(p1c1+1dp1)+φ(p1c1+1)

化简一下,并且提出p1

g(p1c1+1)=p1d|p1c1dφ(p1c1d)+φ(p1c1+1)

可以发现d|p1c1dφ(p1c1d)=g(p1c1),代入

g(p1c1+1)=p1g(p1c1)+φ(p1c1+1)

也就是g(i×primej)=primej×g(i)+φ(i×primej)

总结一下g(x)={1x=12x1xprimeg(i)×g(primej)x=i×primej,gcd(primej,i)=1primej×g(i)+φ(i×primej)x=i×primej,primej|i,lowi=ig(i×primej)=g(ilowi)×g(p1×lowi)x=i×primej,primej|i,lowii

lowx={0i=1xxprimeprimejx=i×primej,gcd(i,primej)=1lowi×primejx=i×primej,primej|i

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=4e6+100;
int prime[MAXN],pcnt,phi[MAXN+1],n;
ll g[MAXN+1],low[MAXN+1],sum[MAXN+1];
bool isp[MAXN+1];
int main() {
	sum[1]=g[1]=phi[1]=1;
	for(int i=2; i<=MAXN; ++i) {//筛出phi,g,low
		if(!isp[i]) {
			prime[++pcnt]=i;
			phi[i]=i-1;
			low[i]=i;
			g[i]=(i<<1)-1;//素数
		}
		for(int j=1,x; j<=pcnt&&(x=i*prime[j])<=MAXN; ++j) {
			isp[x]=true;
			if(i%prime[j]) {
				g[x]=g[i]*g[prime[j]];
				phi[x]=phi[i]*phi[prime[j]];
				low[x]=prime[j];//三个都要算上
			} else {
				low[x]=low[i]*prime[j];
				phi[x]=phi[i]*prime[j];
				g[x]=(low[i]==i)?g[i]*prime[j]+phi[x]:g[i/low[i]]*g[low[i]*prime[j]];
			}//记得特判i=low i
		}
		sum[i]=sum[i-1]+g[i];//前缀和
	}
	while(~scanf("%d",&n)&&n) {
		printf("%lld\n",sum[n]-((n+1ll)*n>>1));//输出
	}
	return 0;
}

P2257 YY的GCD

这题是要求:pprimei=1nj=1m[gcd(i,j)=p]

i,j除以p

pprimei=1npj=1mp[gcd(i,j)=1]

可以对后面的部分进行莫比乌斯反演

f(x)表示gcd(i,j)x的二元组的个数,g(x)表示gcd(i,j)x的倍数的二元组的个数,

把它俩用式子写就是:

f(x)=i=1npj=1mp[gcd(i,j)=x]

g(x)=i=1npj=1mp[x|gcd(i,j)]=i=1npj=1mpx|d[gcd(i,j)=d]

后面的x|d可以提前

g(x)=x|di=1npj=1mp[gcd(i,j)=d]=x|df(x)

根据莫比乌斯反演第二形式,可得:f(x)=x|dg(d)μ(dx)

f(1)=1|dg(d)μ(d1)=i=1npg(i)μ(i)

再看g(x)=i=1npj=1mp[x|gcd(i,j)]这个式子,把i,j除以x可以得到

g(x)=i=1npxj=1mpx[1|gcd(i,j)]=i=1npxj=1mpx1=npxmpx

代回f(1)=i=1npg(i)μ(i)=i=1npnpxmpxμ(i)

提出npxmpx

f(1)=npxmpxi=1npμ(i)

再代回原来的式子:

pprimenpxmpxi=1npμ(i)

先用T枚举px,再枚举p|T

i=1TnTmTp|T&pprimeμ(i)

由于10000128以内的素数只有664583个,所以我们暴力计算后面的部分都不会超时。

前面的部分就可以用整除分块优化

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=10000129;
int prime[MAXN>>3],pcnt;
bool vis[MAXN];
int mu[MAXN];
ll sum[MAXN],g[MAXN];
int read() {
    int x=0,c=getchar();
    bool f=1;
    while(c<=47||c>=58) {
        f=f&&(c^45);
        c=getchar();
    }
    while(c>=48&&c<=57) {
        x=(x<<3)+(x<<1)+(c&15);
        c=getchar();
    }
    return f?x:-x;
}
int main() {
    vis[0]=vis[1]=1;
    mu[1]=1;
    for(int i=2; i<MAXN; ++i) {//预处理mu
        if(!vis[i]) {
            prime[++pcnt]=i;
            mu[i]=-1;
        }
        for(int j=1; i*1ll*prime[j]<MAXN&&j<=pcnt; ++j) {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)break;
            else mu[i*prime[j]]=-mu[i];
        }
    }
    for(int i=1; i<=pcnt; ++i) {
        for(int j=1; j*1ll*prime[i]<MAXN; ++j) {
            g[j*prime[i]]+=mu[j];
        }
    }
    for(int i=1; i<MAXN; ++i) {//预处理后面部分的前缀和
        sum[i]=sum[i-1]+g[i];
    }
    int t=read(),n,m,l,r;
    ll ans;
    while(t--) {
        n=read(),m=read();
        if(n>m)swap(n,m);
        ans=0;
        for(l=1; l<=n; l=r+1) {//整除分块
            r=min(n/(n/l),m/(m/l));
            ans+=(n/l)*(sum[r]-sum[l-1]+0ll)*(m/l);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

P1829 [国家集训队]Crash的数字表格 / JZPTAB

这题是要求:i=1nj=1mlcm(i,j)

根据lcm的性质:

i=1nj=1mijgcd(i,j)

加入一个d=gcd(i,j)

d=1ni=1nj=1m[gcd(i,j)=d]ijd

i,j都除以d

d=1ni=1ndj=1md[gcd(i,j)=1]ijd

提出d,i,把ϵ=Iμ代入:

d=1ndi=1ndij=1mdjk|gcd(i,j)μ(k)

k|gcd(i,j)其实就是k|i&k|j(手玩一下可以知道)

d=1ndi=1ndij=1mdjk|i&k|jμ(k)

直接把k提前:

d=1ndk=1ndμ(k)k|i&i<ndik|j&j<mdj

i,j都除以k

d=1ndk=1ndμ(k)i=1ndkikj=1mdkjk

把两个k都提出来,把d乘进去,再设S(x)=1+2++x=x(x+1)2

d=1nk=1nddk2μ(k)S(ndk)S(mdk)

先枚举T=dk,再枚举k|T

T=1nk|TTkμ(k)S(nT)S(mT)

再提出TS(nT)S(mT)

T=1nTS(nT)S(mT)k|Tkμ(k)

我们设f(T)=k|Tkμ(k),看一下它的性质:

首先id,μ都是积性函数,那么id×μ也是积性函数,而且I也是积性函数,那么f=(id×μ)I也是积性函数。

那么欧拉筛这个f还需要看prime[j]|T的情况:

我们设T分解质因数后为p1k1p2k2pckc(不妨设其中p1=prime[j])则

f(Tp1)f(T)=k|Tp1kμ(k)k|Tkμ(k)=k|Tp1&k|Tkμ(k)

Tp1=p1k1+1p2k2pckc,那么满足k|Tp1&k|T的数必须是p1k1+1的倍数,而且k11k1+12那么μ(k)=0,那么后面那部分就是0,我们就得到:

f(Tp1)=f(T)

最后总结一下:f(Tp1)={f(T)p1|Tf(T)f(p1)p1|T

最后要求T=1nTS(nT)S(mT)f(T),一边线性筛一边求就行了:

#include<bits/stdc++.h>
using namespace std;
const int mod=20101009;
const int MAXN=1e7+128;
typedef long long ll;
int n,m,ans;
bool isp[MAXN+1];
int prime[MAXN+1],f[MAXN+1],pcnt;
inline int S(int x) {
	return (x*1ll*(x+1)/2)%mod;
}
int main() {
	cin>>n>>m;
	if(n>m)swap(n,m);
	f[1]=1,isp[1]=true;
	ans=(ans+S(n)*1ll*S(m))%mod;//先加上T=1时的值
	for(int i=2; i<=n; ++i) {//线性筛f(T)
		if(!isp[i]) {
			prime[++pcnt]=i;
			f[i]=mod-i+1;
		}
		for(int j=1,x; j<=pcnt&&(x=i*prime[j])<=MAXN; ++j) {
			isp[x]=true;
			if(i%prime[j]) {
				f[x]=f[i]*1ll*f[prime[j]]%mod;
			} else {
				f[x]=f[i];
				break;
			}
		}
		ans=(ans+S(n/i)*1ll*S(m/i)%mod*i%mod*f[i])%mod;//同时计算答案
	}
	printf("%d\n",ans);
	return 0;
}
posted @   mod998244353  阅读(183)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
Live2D
欢迎阅读『莫比乌斯反演』
点击右上角即可分享
微信分享提示