1. 程式人生 > >莫比烏斯反演(證明+總結+例題)

莫比烏斯反演(證明+總結+例題)

約數 def scanf 比較 space span net 重復 tex

【學習筆記】莫比烏斯反演

可能最常見的定義式是這樣的:
\[ (2)F(x)=\Sigma_{d|x}f(d) \leftrightarrow f(x)=\Sigma_{d|x}\mu(d)F(x/d) \\or \(3)F(x)=\Sigma_{x|d}f(d) \leftrightarrow f(x)=\Sigma_{x|d}\mu(d)F(d/x) \]
實際上我們喜歡的是這個性質
\[ (1)\Sigma_{d|n}\mu(n)=[n=1] \]
假如一個式子裏有乘積+約數或者什麽什麽+布爾值,那就套一下這個公式。

這個式子如何證明:

考慮枚舉因數就相當於對於\(n=\prod p_i^{a_i}\)

選不同的乘起來,你知道當\(d\)在一個\(p_i\)中選了超過一的指數,由於此時\(\exist x^2|n\)所以\(\mu(n)=0\)我們就可以不考慮它了。

所以情況只剩下選擇不同的\(p_i\)了,等式就變成了我們選幾個不同的素因子:\(\Sigma C_n^i (-1)^i\),我們直接二項式定理可以得到等式\(=(-1+1)^n=0\)

\(n=1\) 時的證明顯然。

那麽我們承認\((1)\)是我們的定義,證明\((2)(3)\)

都是我自己證的,相信一定比較好理解
\[ (1)->(2) \\\Sigma_{d|x}\mu(d)F(x/d) \=\Sigma_{d|x}\mu(x/d)F(d) \=\Sigma_{d|x}\mu(x/d)\Sigma_{k|d}f(k) \=\Sigma_{k|x}f(k)\Sigma_{d|\frac x k}\mu(d) \=\Sigma_{k|x}f(k)[\frac x k=1] \=f(x) \]

  • \((1)->(2)\)
    • \(\Sigma_{d|x}\mu(d)F(x/d)\)
    • \(=\Sigma_{d|x}\mu(x/d)F(d)\dots\)等價寫法
    • \(=\Sigma_{d|x}\mu(x/d)\Sigma_{k|d}f(k)\dots\)套條件
    • \(=\Sigma_{k|x}f(k)\Sigma_{d|\frac x k}\mu(d)\dots\)等價寫法
    • \(=\Sigma_{k|x}f(k)[\frac x k=1]\dots\)\((3)\)的公式
    • \(=f(x)\)
  • \((1)->(3)\)
    • \(\Sigma_{x|d}\mu(d)F(d/x)\)
    • \(=\Sigma_{x|d}\mu(d/x)F(d)\dots\)
      等價寫法
    • \(=\Sigma_{x|d}\mu(d/x)\Sigma_{d|k}f(k) \dots\)套條件
    • \(=\Sigma_{x|k}f(k)\Sigma_{x|d,d|k}\mu(d/x)\dots\)改變枚舉順序,先枚舉\(k\)
    • \(=\Sigma_{x|k}f(k)\Sigma_{d|\frac d x}\mu(d)\dots\)等價寫法
    • \(=\Sigma_{x|k}f(k)[\frac k x=1]\dots\)\((3)\)的公式
    • \(=f(x)\)

好吧我承認這樣寫有點不負責,因為"等價寫法"在我看來是墜難的,因為我想了一節晚自習。

\(\text{talk is cheap , show me the 例題:}\)

P2257 YYb的GCD

題意:求:
\[ \Sigma_{i=1}^n \Sigma_{j=1}^m [(i,j) \in p] \]
(\([\ ]=\lfloor \rfloor\))
\[ =\Sigma_{i=1}^n \Sigma_{j=1}^m [(i,j) \in p] \=\Sigma_{x\in p}\Sigma_{i=1}^n\Sigma_{j=1}^m[(i,j)=x] \=\Sigma_{x\in p}\Sigma_{i=1}^{n/d}\Sigma_{j=1}^{m/d}[(i,j)=1] \=\Sigma_{x\in p}\Sigma_{i=1}^{n/d}\Sigma_{j=1}^{m/d}\Sigma_{d|(i,j)}\mu(d) \=\Sigma_{x\in p}\Sigma_{d=1}^{min(m,n)/x}\mu(d)[n/dk][m/dk] \\assuem:T=dk \=\Sigma_{x\in p}\Sigma_{d=1}^{min(m,n)/x}\mu(d)[n/T][m/T] \=\Sigma_{T=1}^{min(m,n)}[n/T][m/T]\Sigma_{x\in p,x|T}\mu(T/x) \\assume:f(T)=\Sigma_{x\in p,x|T}\mu(T/x) \=\Sigma_{T=1}^{min(m,n)}[n/T][m/T]f(T) \]
預處理\(f(T)\)數組和他的前綴和,就可以數論分塊\(O(n^{1.5})\)了。

怎麽處理\(f(T)\) 呢?可以考慮先定位一個素數\(p\),然後直接\(for(i=1\text{~}1e7)\),把所有\(f(p \times i)+=\mu(i)\)。復雜度\(O(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)=\Sigma_{x|i}\Sigma_{y|j}[(x,y)=1]\)

繼續
\[ =\Sigma_i^n \Sigma_j^md(ij) \=\Sigma_i^n \Sigma_j^m\Sigma_{x|i}\Sigma_{y|j}[(x,y)=1] \=\Sigma_i^n \Sigma_j^m\Sigma_{x|i}\Sigma_{y|j}\Sigma_{d|(x,y)}\mu(d) \=\Sigma_i^n \Sigma_j^m\Sigma_{x|i}\Sigma_{y|j}\Sigma_{d=1}^{min(m,n)}\mu(d) \times [d|(x,y)] \=\Sigma_{d=1}^{min(m,n)}\mu(d)\Sigma_i^n \Sigma_j^m\Sigma_{x|i}\Sigma_{y|j}[d|(x,y)] \=\Sigma_{d=1}^{min(m,n)}\mu(d)\Sigma_x^n \Sigma_y^m \lfloor \frac n x \rfloor\lfloor \frac m y\rfloor[d|(x,y)] \=\Sigma_{d=1}^{min(m,n)}\mu(d)\Sigma_x^{\lfloor \frac n d \rfloor} \lfloor \frac n {dx} \rfloor\Sigma_y^{\lfloor \frac m d \rfloor} \lfloor \frac m {dy}\rfloor \]

右邊有兩個\(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(莫比烏斯反演)

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)=\Sigma_{x|d}f(d) \]
這不就是莫比烏斯反演可以解決的嘛 piece of cake
\[ f(x)=\Sigma_{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=c[n][4];
        memset(buk,0,sizeof buk);
        for(register int t=1,data;t<=n;++t){
          ++buk[data=qr(1)];
          for(register int i=2;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;
}

莫比烏斯反演(證明+總結+例題)