[数学提高] 1 莫比乌斯反演

莫比乌斯反演

没想到吧,真的有莫比乌斯反演专题!我现在已经看不懂我当时在写什么了!

莫比乌斯函数

1. 定义

由唯一分解定理,可以将正整数n写成n=i=1kpiai=p1a1p2a2..pkak的形式,莫比乌斯函数μ(n)的定义为

μ(n)={1n=10i,ai2(1)ki,ai=1

2. 性质

性质1

d|nμ(d)={1n=10n1

证明:设dn的约数,则d=i=1kpibi,其中0biai

对于μ(d),如果bi2,则μ(d)=0。因此,有贡献的μ(d)一定为Cki×(1)i,也就是每个质数最多取一次。

d|nμ(d)=i=0kCki×(1)i,又(ab)k=i=0kCkiakbki

(11)k=i=0kCki×(1)k,故d|nμ(d)=0k=0

3. 与其他数论函数的关系

(1) μI=e

证明:设n=i=1kpiai,n=i=1kpi

(μI)(n)=d|nμ(d)=d|nμ(d)=i=0k(1)i

呃,等等,好像性质一已经证明过了啊。(μI)(n)=[n=1]=e

因此,μI的狄利克雷逆。

(2) μid=φ

这个在基础篇的性质证明过了QWQ,不写辣

(3) μd=I

证明:(II)(n)=d|nI(d)=d|n1=d(n)

d=II,又μ=I1

μd=I

4. 线性筛法求莫比乌斯函数
void Mobius(int n){
	mu[1] = 1;
	for (int i=2;i<=n;++i){
		if (!st[i]) p[++cnt] = i, mu[i] = -1;
		for (int j=1;p[j]<=n/i;++j){
			st[p[j] * i] = true;
			if (i % p[j] == 0) break;
			mu[p[j] * i] = -mu[i];
		}
	}
}
// 当i为质数时, 显然mu[i]=-1
// 当p[j]为i的最小质数时, 就说明p[j]这个质数出现了>1次, 因此mu[i * p[j]] = 0
// 否则
// (1) mu[i]=0, mu[p[j] * i] = 0
// (2) mu[i]不为0, p[j] * i就相当于增加了一个质数, 因此mu[p[j] * i] = -mu[i]

莫比乌斯反演

莫反的函数定义和转换过程大多依靠平时积累,见过类似套路,就会,没见过,就寄。

1. 定义

f(n)为数论函数(定义在正整数集合上的函数)

因数形式:

  • F(n)=fI=d|nf(d)f(n)=d|nμ(d)×F(nd)

证明(利用狄利克雷卷积):因为F(n)=fI,则f=FI1=Fμ

f(n)=d|nμ(d)×F(nd)

证明(利用性质1+二重积分交换次序的思想):

d|nμ(d)×F(nd)=d|nμ(d)×i|ndf(i)=i|nf(i)d|niμ(d)

i能取到所有d可以取到的取值,这样反过来看,把i提到前面)

又当且仅当n=i时,d|niμ(d)=1,因此d|nμ(d)×F(nd)=f(n)

倍数形式:

  • F(n)=n|Nf(N)f(n)=n|NF(N)μ(Nn),(枚举Nn的所有倍数,N[n,+)

证明:n|NF(N)μ(Nn)=n|Nμ(Nn)N|if(i)

d=Nn,则N=dn,则dn|i,即d|in

因此n|Nμ(Nn)N|if(i)=d|inμ(d)N|if(i)

又当且仅当n=i时,d|niμ(d)=1,因此f(n)=n|NF(N)μ(Nn)

运用莫反的时候,通常都是因为F(n)好求,但是f(n)不好求,因此将f(n)F,μ表示出来。

2. 应用1:莫反+整数分块

p2522 Problem b

数据范围:1n,k5×104;1ab5×104;1cd5×104

思路:详细的整理一下吧。

首先,题目要我们求的东西,可以先拆成一个二维前缀和,A[a,b][c,d]=A[1,b][1,d]A[1,b][1,c1]A[1,a1][1,d]+A[1,a1][1,c1]

f(k)=x=1ay=1b[(x,y)=k],然后我们方便求的是这个F(k)=x=1ay=1b[k|(x,y)],且F(k)=k|Nf(N)

则代入莫反倍数形式得f(k)=k|Nμ(Nk)F(N)

先求F(N)。首先,N|(x,y),也就是说,N|x,N|y,因此所有满足条件的点对数量为aN×bN

f(k)=k|Nμ(Nk)ak×bk,设$t=\frac N k t$的结果为1,2,..,这样的整数,N=tk

f(k)=tμ(t)atk×btk,再运用整数分块的知识进行求解即可,注释都写在代码里吧。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> pii;
typedef pair<ll,ll> pll;
#define xx first
#define yy second
#define ls (oo << 1)
#define rs (oo << 1 | 1)
#define PI acos(-1.0)

ll read(void);

int n, cnt;
const int N = 5e4 + 5; 
int p[N], mu[N];
int pre[N];
bool st[N];

//求Mobius函数和前缀和(分块的时候用)
void Mobius(int n){
	mu[1] = 1;
	for (int i=2;i<=n;++i){
		if (!st[i]) p[++cnt] = i, mu[i] = -1;
		for (int j=1;p[j]<=n/i;++j){
			st[p[j] * i] = true;
			if (i % p[j] == 0) break;
			mu[p[j] * i] = -mu[i];
		}
	}
	for (int i=1;i<=n;++i){
		pre[i] = pre[i - 1] + mu[i];
	}
}

ll f(int a, int b, int k){
    a /= k, b /= k;
	ll res = 0, n = min(a, b), l = 1, r;
    // 在[l,r]这段,(a/l)*(b/l)为定值,那么展开和式, 可以打包计算这一部分的和为(定值*mu的前缀和)
	while (l <= n){
		r = min(n, min(a / (a / l), b / (b / l)));
		res += 1LL * (pre[r] - pre[l - 1]) * (a / l) * (b / l);
		l = r + 1;
	}
	return res;
}

void solve(){
	int a, b, c, d, k;
	a = read(), b = read(), c = read(), d = read(), k = read();
    // 二维前缀和,或者说一个简单的容斥
	ll res = f(b, d, k) - f(b, c - 1, k) - f(a - 1, d, k) + f(a - 1, c - 1, k);
	printf("%lld\n", res);
}

int main(void){
	int T;
	Mobius(N - 1);
	T = read();
	while (T--){
		solve();
	}
	
	return 0;
}

ll read(void){
    ll x = 0, f=1;char ch;
    do{ch = getchar();if (ch == '-') f=-1;}while(ch<'0' || ch>'9');
    do{x = x*10 + (ch-'0');ch = getchar();}while(ch>='0' && ch<='9');
    return x*f;
}

/*
敬告kz: 
====================================
  1. 相信自己 
  2. 看清题意, 考虑清楚再动手 
  3.   **** 今天的数组有没有开小呀 ? ****  **** 今天的数组有没有开小呀 ? ****
  4. 是不是想复杂了? 
  5. 数据溢出?
  6. 数组越界?边界情况? 
  6. 不要犯低级错误!!! 时间复杂度?空间复杂度?精度有没有问题? 
====================================
* 提交的时候注意看编译器!c++17 / c++20 / python3 
*/ 
3. 应用2:莫反+提取公因数

p3327约数个数和 莫反+双分块

d(x)x的约数个数,给定Tn,m,求i=1Nj=1Md(i×j)

数据范围:1N,M,T5×104

i=1Nj=1Md(i×j)=i=1Nj=1Mx|iy|j[(x,y)=1]

证明:设i=i=1kpiai,j=i=1kpibi0ai,bi

i×j=i=1kpiai+bid(i×j)=i=1k(ai+bi+1)

即从i中选出约数xj中选出约数y,对于p1而言,若要求(x,y)=1

则可以x=1,y=1,或者x=1,y=∈[p1,p1b1],或者x[p1,p1a1],y=1

一共是(a1+b1+1)种取法,其他质数同理。根据乘法原理,这些取法正好就是d(i×j)

  • 设出f(n),F(n)

f(n)=i=1Nj=1Mx|iy|j[(x,y)=n],显然f(1)就是答案。

F(n)=i=1Nj=1Mx|iy|j[n|(x,y)],则F(n)=n|df(d)

f(n)=n|dμ(dn)F(d)T=min(N,M),则f(1)=d=1Tμ(d)F(d)

  • 再化简F

F(n)=i=1Nj=1Mx|iy|j[n|(x,y)]=x=1Ny=1MNxMy[n|(x,y)]

证明:首先,x|i,y|j,那么x,y肯定是能取到[1,N],[1,M]的。当x,y固定后,[n|(x,y)]i,j是没有关系的,我们可以把它提出来。那么,里面就变成了i=1Nxj=1My1,也就是N,M里面有多少个i,j,它们是x,y的倍数,得证。

下面再消掉[n|(x,y)]这个条件。

x=xn,y=yn

F(n)=x=1Ny=1MNxMy[n|(x,y)]=x=1Nny=1MnNnxMny

N=Nn,M=Mn

F(n)=x=1Ny=1MNxMy=(x=1NNx)×(y=1MMy)

h(n)=i=1nni),也就是标准整数分块,则F(n)=h(N)×h(M)

  • 再求f(1)

f(1)=d=1Tμ(d)h(Nd)h(Md)

由于h(x)只和x有关,所以可以再分一次块,因此每次查询复杂度O(N),总时间复杂度O(NN)

int cnt;
const int N = 5e4 + 5;
int p[N], h[N], pre[N], mu[N];
bool st[N];

void Mobius(int n){
	mu[1] = 1;
	for (int i=2;i<=n;++i){
		if (!st[i]) p[++cnt] = i, mu[i] = -1;
		for (int j=1;p[j]<=n/i;++j){
			st[p[j] * i] = true;
			if (i % p[j] == 0) break;
			mu[p[j] * i] = -mu[i];
		}
	}
	for (int i=1;i<=n;++i){
		pre[i] = pre[i - 1] + mu[i];
	}
}

void H(int n){
	for (int i=1;i<=n;++i){
		for (int l=1, r;l<=i;l=r + 1){
			r = min(i, i / (i / l));
			h[i] += (r - l + 1) * (i / l); 
		}
	}
}

void solve(){
	int n, m;
	n = read(), m = read();
	ll res = 0;
	int k = min(n, m);
	for (int l=1, r;l<=k;l=r + 1){
		r = min(k, min(n / (n / l), m / (m / l)));
		res += (ll)(pre[r] - pre[l - 1]) * h[n / l] * h[m / l];
	}
	printf("%lld\n", res);
}

int main(void){
	int T;
	Mobius(N - 1);
	H(N - 1);
	T = read();
	while (T--){
		solve();
	}
	
	return 0;
}
posted @   跳岩  阅读(538)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示