1. 程式人生 > >淺談算法——莫比烏斯反演

淺談算法——莫比烏斯反演

unsigned 命運 stream getchar() 說明 min pan des ram

前言

莫比烏斯反演(又稱懵逼鎢絲繁衍),那種讓人看了就懵逼的東西(其實是我太菜了)
莫比烏斯反演在知道之後對解題十分有幫助,\(O(n)\)的柿子分分鐘化成\(O(\sqrt n)\)
那麽,什麽是莫比烏斯反演呢?

莫比烏斯反演

1.莫比烏斯反演
如果說,有\(f(n)\)\(g(n)\)是定義在正整數集合上的兩個函數,並且滿足
\[f(n)=\sum\limits_{d|n} g(d)\]
那麽就有
\[g(n)=\sum\limits_{d|n} f(\dfrac{n}{d})\times \mu(d)\]
這個變形也就是莫比烏斯反演

2.莫比烏斯函數(性質/證明)
對於任意整數n,滿足
\[\sum\limits_{d|n} \mu(d)=\begin{cases}1&,n=1\\0&,n>1\end{cases}\]


證明:

  • n=1時顯然
  • n>1時,設\(n=P_1^{x_1}\times P_2^{x_2}\times ...\times P_k^{x_k}\),其中\(P_i(1\leqslant i\leqslant k)\)為n的互質因子,我們設\(d=P_1^{y_1}\times P_2^{y_2}\times ...\times P_k^{y_k}\),其中\(0\leqslant y_i\leqslant x_i(1\leqslant i\leqslant k)\),當存在一個\(y_i\geqslant2\)的話,那麽\(\mu(d)=0\),因此我們只要考慮\(y_i=0,1\)的情況。假設d中含有r個n的互質因子,且這些質因子的指數為1,那麽\(\mu(d)=(-1)^r\)
    ,這樣的d一共有\(\binom{k}{r}\)個。
    那麽\(\sum\limits_{d|n} \mu(d)=\sum\limits_{r=0}^k \binom{k}{r}\times (-1)^r=(1-1)^k=0\)

莫比烏斯函數\(\mu(n)\)是個積性函數,證明略。
這說明\(\mu(n)=\prod\limits_{i=1}^k \mu(P_i^{x_i})\)


由於莫比烏斯函數的這些性質,使得它可以用歐拉篩在線性時間內求出

3.莫比烏斯反演(性質/證明)
首先我們證明一下第一個柿子的正確性
\[g(n)=\sum\limits_{d|n}f(\dfrac{n}{d})\times \mu(d)=\sum\limits_{d|n}\mu(d) \sum\limits_{i|\frac{n}{d}}g(i)\Longrightarrow\sum\limits_{i|n}g(i)\sum\limits_{i\times d|n}\mu(d)=\sum\limits_{i|n}g(i)\sum\limits_{d|\frac{n}{i}}\mu(d)\]


箭頭轉換的時候,將\(g(i)\)提前枚舉了
我們現在考慮一下\(\sum\limits_{d|\frac{n}{i}}\mu(d)\)的取值

  • 當i=n時,\(\sum\limits_{d|\frac{n}{i}}\mu(d)=1,g(i)\sum\limits_{d|\frac{n}{i}}\mu(d)=g(n)\)
  • 當i為小於n的約數時,\(\sum\limits_{d|\frac{n}{i}}\mu(d)=0,g(i)\sum\limits_{d|\frac{n}{i}}\mu(d)=0\)

所以\(g(n)=\sum\limits_{d|n}f(\dfrac{n}{d})\times \mu(d)\)得證


莫比烏斯函數有個性質:
\(f(n)/g(n)\)中的任意一個為積性函數後,另一個也同樣為積性函數
這個性質保證了其在之後能夠進行線篩。
證明略(證明太長,我懶得寫)

4.莫比烏斯函數的應用

  • \(g(i)\)很難直接求,但\(\sum\limits_{d|i}g(d)\)很好求,我們就可以用莫比烏斯反演來求\(g(i)\):\[f(i)=\sum\limits_{d|i}g(d)\Rightarrow g(i)=\sum\limits_{d|i}f(\dfrac{i}{d})\times \mu(d)\]
  • \(g(i)\)很難直接求,但\(\sum\limits_{d=1}^{\frac{n}{i}}\)很好求,我們就可以用莫比烏斯反演來求\(g(i)\):\[f(i)=\sum\limits_{d=1}^{\frac{n}{i}}g(d\times i)\Rightarrow g(i)=\sum\limits_{d=1}^{\frac{n}{i}}f(d\times i)\mu(d)\]

例題

[POI2007]Zap
Description
FGD正在破解一段密碼,他需要回答很多類似的問題:對於給定的整數a,b和d,有多少正整數對x,y,滿足x<=a,y<=b,並且gcd(x,y)=d。作為FGD的同學,FGD希望得到你的幫助。

Input
第一行包含一個正整數n,表示一共有n組詢問。(1<=n<= 50000)接下來n行,每行表示一個詢問,每行三個正整數,分別為a,b,d。(1<=d<=a,b<=50000)

Output
對於每組詢問,輸出到輸出文件zap.out一個正整數,表示滿足條件的整數對數。

Sample Input
2
4 5 2
6 4 3

Sample Output
3
2

HINT
對於第一組詢問,滿足條件的整數對有(2,2),(2,4),(4,2)。對於第二組詢問,滿足條件的整數對有(6,3),(3,3)。

Attention:為了方便書寫,我們把x,y改成i,j,把多組數據的n改為T,把a,b改成n,m,並且我們讓n<m

首先我們列出本題要求的柿子
\[\sum\limits_{i=1}^n\sum\limits_{j=1}^m[gcd(i,j)==d]\]
d看起來太討厭,我們把它直接除掉
\[\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d}\rfloor}[gcd(i,j)==1]\]
然後我們發現最後面那一個長得和莫比烏斯函數很像,稍微改一下得到
\[\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d}\rfloor}\sum\limits_{x|i,x|j}\mu(x)\]
把枚舉x的挪到前面
\[\sum\limits_{x=1}^{\lfloor\frac{n}{d}\rfloor}\mu(x)\lfloor\dfrac{n}{dx}\rfloor\lfloor\dfrac{m}{dx}\rfloor\]
這個柿子終於變得小清新了,但是如果直接枚舉x的話還是擺脫不了TLE的命運。。。
註意到後面的\(\lfloor\dfrac{n}{dx}\rfloor\lfloor\dfrac{m}{dx}\rfloor?\)在很多情況下值都是一樣的,那麽我們進行數論分塊即可
總時間復雜度降為\(O(T\sqrt n)\)

/*program from Wolfycz*/
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline int read(){
    int x=0,f=1;char ch=getchar();
    for (;ch<‘0‘||ch>‘9‘;ch=getchar())  if (ch==‘-‘)    f=-1;
    for (;ch>=‘0‘&&ch<=‘9‘;ch=getchar())    x=(x<<1)+(x<<3)+ch-‘0‘;
    return x*f;
}
inline void print(int x){
    if (x>=10)     print(x/10);
    putchar(x%10+‘0‘);
}
const int N=5e4;
int prime[N+10],miu[N+10],sum[N+10];
bool inprime[N+10];
void prepare(){
    miu[1]=1;
    int tot=0;
    for (int i=2;i<=N;i++){
        if (!inprime[i])    prime[++tot]=i,miu[i]=-1;
        for (int j=1;j<=tot&&i*prime[j]<=N;j++){
            inprime[i*prime[j]]=1;
            if (i%prime[j]==0){miu[i*prime[j]]=0;break;}
            miu[i*prime[j]]=-miu[i];
        }
    }
    for (int i=1;i<=N;i++)  sum[i]=sum[i-1]+miu[i];
}
int main(){
    prepare();
    for (int Data=read();Data;Data--){
        int A=read(),B=read(),D=read();
        ll Ans=0;
        A/=D,B/=D;
        int x=min(A,B),pos=0;
        for (int d=1;d<=x;d=pos+1){
            pos=min(A/(A/d),B/(B/d));
            Ans+=1ll*(sum[pos]-sum[d-1])*(A/d)*(B/d);
        }
        printf("%lld\n",Ans);
    }
    return 0;
}

淺談算法——莫比烏斯反演