cout << "Hello World!" << endl;|

xzm111

园龄:2年粉丝:1关注:0

莫比乌斯反演

莫比乌斯函数

定义

定义莫比乌斯函数

(1)μ(x)={0x11x=1(1)kx1k=x

即:在 x1 时,对 x 分解质因数,如果其中有一个质因子的幂次大于等于 2 ,则有 μ(x)=0 ;否则,若 x 有奇数个质因子,则 μ(x)=1 ,若 x 有偶数个质因子,则 μ(x)=1

x=1 时,有 μ(x)=1

性质

  1. 莫比乌斯函数是积性函数

    即:对于任意互质的整数 a,b ,都有 μ(ab)=μ(a)μ(b) ,利用这个性质,可以利用欧拉筛在 Θ(n) 的时间内求出 inμ(i) ,并可用杜教筛快速计算 i=1nμ(i)

  2. 莫比乌斯函数 μ 在迪利克雷卷积中为常数函数 1 的逆元。

    即:对任意数论函数 f(x)g(x) ,若有 f(n)=d|ng(d) 则有 g(n)=d|nf(d)μ(nd) ,这里数论函数 $g(n) 即为数论函数 f(n) 的莫比乌斯反演。

重要结论

  1. ϵ(x)=d|xμ(d)

(2)d|nμ(d)={0x11x=1

证明:由性质 2 可以推出 μ1=ϵ ,写成迪利克雷卷积即可。

  1. [gcd(i,j)=1]=d|gcd(i,j)μ(d)

    证明:由结论1可得,d|gcd(i,j)μ(d)=ϵ(gcd(i,j)) ,当且仅当 gcd(i,j)=1 时,其值为 ϵ(1)=1

莫比乌斯函数求值

莫比乌斯反演一般会用到莫比乌斯函数的前缀和,下面提供两种计算方法,各有优劣。

欧拉筛

欧拉筛适用于对积性函数求值,上文提到 μ 为积性函数,故可以利用欧拉筛在 Θ(n) 的时间内求出 inμ(i)

通过欧拉筛求积性函数的值,需要确定 3 种情况下函数的取值:x 为质数、 x=ypymodp0x=ypymodp=0 ,其中 px 的最小质因子。

  1. x 为质数时,根据定义可得 μ(x)=1

  2. x=ypymodp0 时,一定有 p2|x ,根据定义, mu(x)=0

  3. x=ypymodp=0 时,若 y 含有平方因子,则 μ(x)=μ(y)=0 ;若 y 不含平方因子,根据定义可知:μ(x)=μ(y) 。综上,当 ymodp=0 时, μ(x)=mu(y)

由此可以写出线性时间求解莫比乌斯函数的代码:

inline void Euler(int n) {
	mu[1]=1; //根据定义得
	for(int i=2;i<=n;i++) {
		if(!vis[i]) mu[i]=-1,pr[++pcnt]=i; //情况1:质数
		for(int j=1;j<=pcnt&&i*pr[j]<=n;j++) {
			vis[i*pr[j]]=true;
			if(i%pr[j]==0) {
				mu[i*pr[j]]=0;break; //情况2:相同质因子
			}
			mu[i*pr[j]]=-mu[i]; //情况3:新的不同质因子
		}
	}
}

杜教筛

有时题目要求快速计算 μ(i) 的前缀和,即 i=1nμ(i) 的值,这时杜教筛可以在低于 Θ(n) 的时间内求出前缀和在某个位置上的值。

杜教筛要求找出一个 g(x) 与当前函数 f(x) 做迪利克雷卷积,且卷积的前缀和好求,而根据之前提到的性质: μ1=ϵf(x) 直接取常数函数 1 即可。

定义 sum(x)=i=1xμ(i)

根据杜教筛的式子 g(1)sum(n)=i=1n(fg)(i)i=2ng(i)sum(ni) ,用常数函数 1 替换 g ,用 μ 替换 f 可得: sum(n)=i=1nϵ(i)i=2nsum(ni) ,显然, i=1nϵ(i)=1,而i=2nsum(ni) 可用数论分块在加速计算。

直接递归计算的时间复杂度为 Θ(n34) ,如果提前用欧拉筛计算出出前 n23 项的前缀和,则时间复杂度可以降低至 Θ(n23)

代码:

unordered_map<long long,long long>mp1; //map是O(logn)查询修改,选用unordered_map可以获得更优的O(1)查询修改
long long SumMu(long long n) { //先调用 Euler(MAXN-1) 预处理前 MAXN 项的 sumMu
	if(n<MAXN) return sumM[n]; //如果当前的n在预处理的范围内,就直接返回
	if(mp.find(n)!=mp.end()) return mp[n]; //如果当前的值之前求过,就直接返回
	long long res=1; //epsilon前缀和=1
	for(long long i=2;i<=n;i++) { //数论分块
		int j=n/(n/i);
		res-=1ll*(j-i+1)*SumMu(n/i);
		i=j;
	}
	mp[n]=res; //记忆化加速下次查询
	return res;
}

应用

[POI2007]ZAP-Queries

[POI2007]ZAP-Queries

给出 a,b,d,求 x=1ay=1b[gcd(x,y)=d],多组数据。

上文证明 [gcd(x,y)=1]=d|gcd(x,y)μ(d) ,而题目要求 [gcd(x,y)=d]。考虑将 d 化成 1 ,用 xdxydy,得到 x=1ady=1bd[gcd(x,y)=1]

f(n,m)=i=1nj=1m[gcd(i,j)=1],用莫比乌斯函数替换 gcd(i,j)=1 的条件,即有 f(n,m)=i=1nj=1mk|i,k|jμ(k)。考虑先枚举 k ,发现 k 的范围为 [1,min(n,m)]nm,即可将上式化为 f(n,m)=k=1nμ(k)k|ink|jm1。由于小于 nk 的倍数共有 nk 个,m 同理,则有 f(n,m)=k=1nμ(k)nknk,预处理出 μ 的前缀和后,利用数论分块可在 Θ(n) 的时间内计算 f(n,m)

题目要求的式子就等于 f(ad,bd),利用欧拉筛预处理 μ 的前缀和,即可实现 Θ(n) 预处理, O(n) 回答一次询问。

回答询问代码:

inline int calc(int n,int m) {
	if(n>m) swap(n,m);
	int res=0;
	for(int i=1;i<=n;i++) { //数论分块
		int j=min(n/(n/i),m/(m/i));
		res+=(sum[j]-sum[i-1])*(n/i)*(m/i); //sum为mu的前缀和
		i=j;
	}
	return res;
}

PGCD - Primes in GCD Table

PGCD - Primes in GCD Table

给定 nm ,求 1xn,1ymgcd(x,y) 为质数的 (x,y) 有多少对,至多 10 次询问。

即是求: i=1nj=1m[gcd(n,m)Prime]。莫比乌斯反演套路:先枚举 gcd(i,j) 的值,可将原式化为 gPrimei=1nj=1m[gcd(i,j)=g] ,继续用上一题的套路,将 g 化为 1,得到 gPrimei=1ngj=1mg[gcd(i,j)=1],转化 gcd(i,j)=1 的条件得到:gPrimei=1ngj=1mgd|i,d|jμ(d)。和上一题一样,令 f(n,m)=i=1nj=1m[gcd(i,j)=1],则有 f(n,m)=k=1nμ(k)nknk,即可将原式化为 gPrimef(ng,mg) ,令 g(x)=[xPrime],显然,g 的前缀和可以在筛出质数后 Θ(n) 预处理,则可以将原式化为 i=1ng(i)f(ng,mg),用数论分块求解即可。

这里是两层数论分块的嵌套,回答一次询问的时间复杂度为 Θ(n34) ,本题询问量不大,可以通过。

回答询问代码:

inline long long f(int n,int m) { //数论分块求f(n,m)
	if(n>m) swap(n,m);
	long long res=0;
	for(int i=1;i<=n;i++) {
		int j=min(n/(n/i),m/(m/i));
		res+=1ll*(smu[j]-smu[i-1])*(n/i)*(m/i);
		i=j;
	}
	return res;
}
inline long long calc(int n,int m) {
	if(n>m) swap(n,m);
	long long res=0;
	for(int i=1;i<=n;i++) {
		int j=min(n/(n/i),m/(m/i));
		res+=1ll*(spr[j]-spr[i-1])*f(n/i,m/i); //spr为g的前缀和
		i=j;
	}
	return res;
}

YY的GCD

YY的GCD

给定 nm ,求 1xn,1ymgcd(x,y) 为质数的 (x,y) 有多少对,至多 104 次询问。

随着询问次数的增加,上一题的方法已经无法通过,考虑优化。

拆开 f 得到:i=1ng(i)j=1ninijmijμ(j),令 k=ij,则原式化为 i=1ng(i)j=1ninkmkμ(ki) ,变换枚举顺序,先枚举 k ,得到 k=1ni|kg(i)μ(ki)nkmk。可以发现,最后两项与 i 无关,可以提到前面,得到 k=1nnkmki|kg(i)μ(ki)。令 h(x)=i|xg(i)μ(ki),即有 h(x)=iPrime,i|xμ(ii)。推导至此,即可在欧拉筛后枚举每个质数的倍数计算 h(x) 的值,认为质数个数约为 nlnn 时,计算 h(x) 的时间复杂度约为为 Θ(n),可以通过。

关键代码:

inline void init(int n) {
	Euler(n); //欧拉筛质数和mu
	for(int i=1;i<=pcnt;i++) { //枚举质数求h的值
		for(int j=1;pr[i]*j<=n;j++) {
			sum[pr[i]*j]+=mu[j];
		}
	}
	for(int i=1;i<=n;i++) {
		smu[i]=smu[i-1]+mu[i];
		sum[i]+=sum[i-1];
	}
}
inline long long calc(int n,int m) {
	if(n>m) swap(n,m);
	long long res=0;
	for(int i=1;i<=n;i++) { //数论分块
		int j=min(n/(n/i),m/(m/i));
		res+=1ll*(sum[j]-sum[i-1])*(n/i)*(m/i);
		i=j;
	}
	return res;
}

当然,h(x)的值也可用欧拉筛在线性时间内筛出.

和之前考虑欧拉筛 μ 一样,仍然考虑欧拉筛会遇到的三种情况:x 为质数、 x=ypymodp0x=ypymodp=0 ,其中 px 的最小质因子。

  1. x 为质数时,由于 1 不是质数,故只会枚举到 i=x 的情况,即当 x 为质数时, h(x)=μ(xx)=μ(1)=1

  2. x=ypymodp0 时,px 相比于 y 多出的一个新的质因子,这会导致 h(y) 中枚举到的 yi 变成 ypi,根据 μ 的定义得出 μ(ypi)=mu(yi),则有μ(xi)=mu(yi)。再考虑由于 p 而多出来的部分,因为枚举到的 i 为质数,故只会多枚举一个 p,则只会多贡献一个 μ(xp)=μ(y)。 由此得出:当 x=ypymodp0 时, h(x)=h(y)+μ(p)

  3. x=ypymodp=0 时,px 的一个幂次大于等于 2 的质因子,根据 μ 定义,由于枚举到的 i 均为质数,故当 ip 时,所有的 μ(xi)=0。最后剩下一个新枚举到的 p ,对结果的贡献为 μ(xp)=μ(y)。由此得出:当 x=ypymodp=0 时, h(x)=μ(y)

据此可以利用欧拉筛在 Θ(n) 的时间内求解 h(x) 的值和前缀和,略快于上一个做法。

关键代码:

inline void Euler(int n) {
	mu[1]=1;f[1]=0; //根据定义得到
	for(int i=2;i<=n;i++) {
		if(!vis[i]) mu[i]=-1,pr[++pcnt]=i,f[i]=1; //情况1
		for(int j=1;j<=pcnt&&i*pr[j]<=n;j++) {
			vis[i*pr[j]]=true;
			if(i%pr[j]==0) {
				mu[i*pr[j]]=0;
				f[i*pr[j]]=mu[i];break; //情况2
			}
			mu[i*pr[j]]=-mu[i];
			f[i*pr[j]]=-f[i]+mu[i]; //情况3
		}
	}
	for(int i=1;i<=n;i++) sum[i]=sum[i-1]+f[i];
}
inline long long calc(int n,int m) {
	if(n>m) swap(n,m);
	long long res=0;
	for(int i=1;i<=n;i++) { //数论分块
		int j=min(n/(n/i),m/(m/i));
		res+=(sum[j]-sum[i-1])*(n/i)*(m/i);
		i=j;
	}
	return res;
}

To be continued...

本文作者:Transparent

本文链接:https://www.cnblogs.com/xzmxzm/p/17207274.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   xzm111  阅读(25)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起