【笔记】莫比乌斯反演
关于他的具体取值取百度百科看一下就好了,不在赘述。
可能最常见的定义式是这样的:
\[ (2)F(x)=\sum_{d|x}f(d) \leftrightarrow f(x)=\sum_{d|x}\mu(d)F(x/d) \\or \\ (3)F(x)=\sum_{x|d}f(d) \leftrightarrow f(x)=\sum_{x|d}\mu(d)F(d/x) \]
一般来说,组合容斥题上面那个,数论题下面这个比较方便。
\[ (1)\sum_{d|n}\mu(d)=[n=1] \]
假如一个式子里有乘积+约数或者什么什么+布尔值,那就套一下这个公式。
这个式子如何证明:
考虑枚举因数就相当于对于\(n=\prod p_i^{a_i}\)选不同的乘起来,你知道当\(d\)在一个\(p_i\)中选了超过一的指数,由于此时\(\exist x^2|n\)所以\(\mu(n)=0\)我们就可以不考虑它了。
所以情况只剩下选择不同的\(p_i\)了,等式就变成了我们选几个不同的素因子:\(\sum C_n^i (-1)^i\),我们直接二项式定理可以得到等式\(=(-1+1)^n=0\)。
\(n=1\) 时的证明显然。
那么我们承认\((1)\)是我们的定义,证明\((2)(3)\)。
都是我自己证的,相信一定比较好理解
- \((1)->(2)\)
- \(\sum_{d|x}\mu(d)F(x/d)\)
- \(=\sum_{d|x}\mu(x/d)F(d)\dots\)等价写法
- \(=\sum_{d|x}\mu(x/d)\sum_{k|d}f(k)\dots\)套条件
- \(=\sum_{k|x}f(k)\sum_{d|\frac x k}\mu(d)\dots\)等价写法
- \(=\sum_{k|x}f(k)[\frac x k=1]\dots\)套\((3)\)的公式
- \(=f(x)\)
- \((1)->(3)\)
- \(\sum_{x|d}\mu(d)F(d/x)\)
- \(=\sum_{x|d}\mu(d/x)F(d)\dots\)等价写法
- \(=\sum_{x|d}\mu(d/x)\sum_{d|k}f(k) \dots\)套条件
- \(=\sum_{x|k}f(k)\sum_{x|d,d|k}\mu(d/x)\dots\)改变枚举顺序,先枚举\(k\)
- \(=\sum_{x|k}f(k)\sum_{d|\frac d x}\mu(d)\dots\)等价写法
- \(=\sum_{x|k}f(k)[\frac k x=1]\dots\)套\((3)\)的公式
- \(=f(x)\)
好吧我承认这样写有点不负责,因为"等价写法"在我看来是坠难的,因为我想了一节晚自习。等价写法不好讲,我只能保证是等价的,具体如何理解评论区讨论吧...
总结一下我们的证明思路,或者说如何感性理解它 :
我们知道\(\sum_{d|n}\mu(d)=[n=1]\),这是一个很强的性质,我们把函数\(f(x)\)乘在\(\mu\)的后面,这样我们函数sum起来就和\(\mu\)有关了,所以我们要令\(n=\frac x m\)使得当\(x=m\)时才有值,这样最后等式只剩下\(f(m)\)了,也达到了我们反演的目的。
考虑这样的等价写法就是考虑两个等式最终加起来的式子是否不重不漏,理论依据是加法交换律和乘法分配律...
\(\text{talk is cheap , show me the 例题:}\)
P2257 YYb的GCD
题意:求:
\[ \sum_{i=1}^n \sum_{j=1}^m [(i,j) \in p] \]
(\([\ ]=\lfloor \rfloor\))
\[ \begin{array} =\sum_{i=1}^n \sum_{j=1}^m [(i,j) \in p] \\ =\sum_{x\in p}\sum_{i=1}^n\sum_{j=1}^m[(i,j)=x] \\ =\sum_{x\in p}\sum_{i=1}^{n/x}\sum_{j=1}^{m/x}[(i,j)=1] \\ =\sum_{x\in p}\sum_{i=1}^{n/x}\sum_{j=1}^{m/x}\sum_{d|(i,j)}\mu(d) \\ =\sum_{x\in p}\sum_{d=1}^{min(m,n)/x}\mu(d)(n/dx)(m/dx) \\assume:T=dx \\ =\sum_{x\in p}\sum_{d=1}^{min(m,n)/x}\mu(d)(n/T)(m/T) \\ =\sum_{T=1}^{min(m,n)}(n/T)(m/T)\sum_{x\in p\text{&} x|T}\mu(T/x) \\assume:f(T)=\sum_{x\in p,x|T}\mu(T/x) \\ =\sum_{T=1}^{min(m,n)}(n/T)(m/T)f(T) \end{array} \]
说明一下第一个assume后面的等价变换:
第一步应该没问题,第二步的意思是,先考虑d和T是什么关系,很明显\(d\)是\(T\)的因子并且\(T/d=\)一个质数,所以我们先枚举一下\(T\),然后再枚举所有质数\(x\),注意到这个质数\(x\)同样\(x|T\),访问\(\mu(T/x)\)的值就访问到了原先的\(d\)。
预处理\(f(T)\)数组和他的前缀和,就可以数论分块\(O(n^{1.5})\)了。
怎么处理\(f(T)\) 呢?可以考虑先定位一个素数\(p\),然后直接\(for(i=1\text{~}1e7)\),把所有\(f(p \times i)+=\mu(i)\)。复杂度\(O(\log n)?\)
#include<bits/stdc++.h>
using namespace std;
inline int qr(){
char c=getchar();
register int ret=0,f=0;
while(not isdigit(c)) f|=c==45,c=getchar();
while( isdigit(c)) ret=ret*10+c-48,c=getchar();
return f?-ret:ret;
}
const int maxn=1e7+5;
typedef long long ll;
bool usd[maxn];
int mu[maxn];
int f[maxn];
ll sum[maxn];
vector < int > pr;
#define pb push_back
inline void gen_mu(){
mu[1]=usd[1]=1;
for(int t=2;t< maxn;++t){
if(not usd[t]) pr.pb(t),mu[t]=-1;
for(auto i:pr)
if(1ll*i*t>=maxn) break;
else if(usd[i*t]=1,t%i) mu[t*i]=-mu[t];
else break;
}
for(auto i:pr)
for(int t=1;1ll*i*t< maxn;++t)
f[i*t]+=mu[t];
for(int t=1;t< maxn;++t) sum[t]=sum[t-1]+1ll*f[t];
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
#endif
ll ans=0;
gen_mu();
for(int T=qr(),n,m;T;T--,ans=0){
n=qr();m=qr();
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+=1ll*(n/l)*(m/l)*(sum[r]-sum[l-1]);
if(not ans) puts("0");
else printf("%lld\n",ans);
}
return 0;
}
[P3327 SDOI2015]约数个数和
先给个式子
\(d(ij)=\sum_{x|i}\sum_{y|j}[(x,y)=1]\)
继续
\[ \begin{array} =\sum_i^n \sum_j^md(ij) \\ =\sum_i^n \sum_j^m\sum_{x|i}\sum_{y|j}[(x,y)=1] \\ =\sum_i^n \sum_j^m\sum_{x|i}\sum_{y|j}\sum_{d|(x,y)}\mu(d) \\ =\sum_i^n \sum_j^m\sum_{x|i}\sum_{y|j}\sum_{d=1}^{min(m,n)}\mu(d) \times [d|(x,y)] \\ =\sum_{d=1}^{min(m,n)}\mu(d)\sum_i^n \sum_j^m\sum_{x|i}\sum_{y|j}[d|(x,y)] \\ =\sum_{d=1}^{min(m,n)}\mu(d)\sum_x^n \sum_y^m \lfloor \frac n x \rfloor\lfloor \frac m y\rfloor[d|(x,y)] \\ =\sum_{d=1}^{min(m,n)}\mu(d)\sum_x^{\lfloor \frac n d \rfloor} \lfloor \frac n {dx} \rfloor\sum_y^{\lfloor \frac m d \rfloor} \lfloor \frac m {dy}\rfloor \end{array} \]
右边有两个\(floor\),可以数论分块。分块后可以直接乘上一段\(\mu\)的前缀和。
#include<bits/stdc++.h>
using namespace std; typedef long long ll;
template < class ccf > inline ccf qr(ccf ret){ret=0;
register char c=getchar();
while(c<48||c>57) c=getchar();
while(c>=48&&c<=57) ret=ret*10+c-48,c=getchar();
return ret;
}inline int qr(){return qr(1);}
#define pb push_back
const int maxn=5e4+5;
bool usd[maxn];
int mu[maxn];
int sum[maxn];
ll pre[maxn];
vector < int > pr;
inline void gen_mu(){
mu[1]=usd[1]=1;
for(register int t=2;t< maxn;sum[t]=sum[t-1]+mu[t],++t){
if(not usd[t]) mu[t]=-1,pr.pb(t);
for(auto i:pr)
if(1ll*i*t>=maxn) continue;
else if(usd[i*t]=1,t%i) mu[i*t]=-mu[t];else break;
}
for(register ll t=1;t< maxn;++t)
for(register ll l=1,r;l<=t;l=r+1)
r=t/(t/l),pre[t]+=1ll*(r-l+1)*(t/l);
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
#endif
gen_mu();
for(register int T=qr(),n,m;T;T--){
n=qr(1);m=qr(1);ll ans=0;
for(register ll l=1,r,edd=min(m,n);l<=edd;l=r+1)
r=min(n/(n/l),m/(m/l)),
ans+=1ll*(0ll+sum[r]-sum[l-1])*pre[n/l]*pre[m/l];
printf("%lld\n",ans);
}
return 0;
}
POJ3094 Sky Code(莫比乌斯反演)
题意
给你\(n\le 10^5\)个数,这些数\(\le 10^5\),问这些这些数组成的互不相同的无序四元组(a,b,c,d)使得gcd(a,b,c,d)=1的四元组有多少?
解法
枚举一个约数\(k\),看看总共有多少个数\(S_k=\{x\}\)满足\(k|x\)。那么可以保证(a,b,c,d)有的一个共同的因子是k,这样的四元组的个数就是
\[ F(k)={|S_k|\choose 4} \]
这样算会算重,比如枚举到\(k=4\)再枚举到\(k=2\),这两者的方案显然有重复,加入有一个四元组满足有一个共同约数是4,那么他们一定也可以满足有一个共同约数是2。我们记\(f(x)=\)最大公因数是\(x\)的四元组的数量。上面的那个大\(F(x)\)就表示有一个公因数(不是最大公因数)是\(x\)的四元组的数量
我们数学模型化这个算重的关系:
\[ F(x)=\sum_{x|d}f(d) \]
这不就是莫比乌斯反演可以解决的嘛 piece of cake
\[ f(x)=\sum_{x|d}\mu(d)F(\frac d x) \]
那么把\(f(1)\)求出来就好了
Q:你这样不是O(n^2)吗,你怎么实现可以在正确的复杂度内得到每个数所有的因数?
A:开个桶表示每个\(|S_k|\),枚举\(i\in [2,\sqrt x]\),把\(i\)和\(x/i\)都丢在桶里计数。复杂度\(O(n^{1.5})\)注意当\(i=x/i\)的时候只算一次!
//@winlere
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std; typedef long long ll;
template < class ccf > inline ccf qr(ccf ret){ ret=0;
register char c=getchar();
while(not isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+c-48,c=getchar();
return ret;
}
const int maxn=1e4+1;
ll c[maxn][5];
int n;
ll ans;
int buk[maxn];
int cnt[maxn];
bool data[maxn];
int mu[maxn];
bool usd[maxn];
vector < int > ve;
inline void pr(){
usd[1]=1;mu[1]=0;
for(register int t=2;t<maxn;++t){
if(not usd[t]) ve.push_back(t),mu[t]=-1;
for(register int i=0,edd=ve.size();i<edd;++i){
register int k=ve[i];
if(1ll*k*t>maxn)break;
usd[k*t]=1;
if(t%k==0) break;
mu[k*t]=-mu[t];
}
}
}
int main(){
c[0][0]=1;
pr();
for(register int t=1;t<maxn;++t){
c[t][0]=1;
for(register int i=1;i<=4;++i)
c[t][i]=c[t-1][i-1]+c[t-1][i];
}
while(~scanf("%d",&n)){
ans=0;
memset(buk,0,sizeof buk);
for(register int t=1,data;t<=n;++t){
data=qr(1);
for(register int i=1;i*i<=data;++i)
if(data%i==0)
if(++buk[i],data!=i*i) ++buk[data/i];
}
for(register int t=1;t<maxn;++t)
if(buk[t]>=4&&mu[t])
ans+=mu[t]*c[buk[t]][4];
printf("%lld\n",ans);
}
return 0;
}