BZOJ1101 ACWING215. 破譯密碼(莫比烏斯函數+容斥原理 / 莫比烏斯反演)

達達正在破解一段密碼,他需要回答很多類似的問題:

對於給定的整數a,b和d,有多少正整數對x,y,滿足x<=a,y<=b,並且gcd(x,y)=d。

作爲達達的同學,達達希望得到你的幫助。

輸入格式
第一行包含一個正整數n,表示一共有n組詢問。

接下來n行,每行表示一個詢問,每行三個正整數,分別爲a,b,d。

輸出格式
對於每組詢問,輸出一個正整數,表示滿足條件的整數對數。

數據範圍
1≤n≤50000,
1≤d≤a,b≤50000
輸入樣例:
2
4 5 2
6 4 3
輸出樣例:
3
2
提示:gcd(x,y)返回x,y的最大公約數。

思路:
首先將a除以k,b除以k,題目就轉換爲了求x ≤ a/k, y ≤ b/k,且x,y互質的二元組數了。

定義D[a,b,i]D[a,b,i]代表$ x ≤ a, y ≤ b時,gcd(a,b) $是 i 倍數的二元組(a,b)個數。

易得D[a,b,i]=[a/x][b/x].D[a,b,i] = [a/x] * [b/x].

定義F[a,b]爲代表xa,ybx ≤ a, y ≤ b且 x,y互質的數個數。

由容斥原理得到
F[a,b]=µ(i)D[a,b,i]1imin(a,b)F[a,b] = ∑µ(i) * D[a,b,i] 1 ≤ i ≤ min(a,b)
具體過程爲,加上 i = 1時候的情況,減去gcd(x,y)爲 2, 3, 5, 7…倍數時候的情況,再加上gcd(x,y)即是2倍數,也是3倍數時候的情況。。。
於是µ(i)恰好爲莫比烏斯函數

又由除法分塊的知識可以得到: 假設 n/i=jn / i = j,那麼n/(n/i)n/i=jn / (n/i)爲n/i=j的最大ii,於是可以連續一段中 [a/x]相等,且[b/x]相等的右端點。
於是分塊的循環,再乘上這一段的莫比烏斯函數前綴和即可。

upd: 又去看了一下網上的題解,發現本題還有莫比烏斯反演的解法,我們在從D函數到F函數的過程,用的是容斥原理,但事實上這個過程可以直接用莫比烏斯反演得到。

初步瞭解了一下莫比烏斯反演,就是這樣一個過程:

如果有(圖自OI WIKI):
在這裏插入圖片描述

那麼可以反演得到:
在這裏插入圖片描述
其中µµ函數代表莫比烏斯函數。

而本題中的D函數其實就代表了上圖中的F函數,F函數就代表了上圖中的g函數。

推導過程爲:

D(n)代表n爲gcd(a,b)約數的二元組數。
F(d)代表d爲gcd(a,b)的二元組數。

D(n)=ndF(d)=NnMnD(n)=\sum_{n|d}F(d)=\lfloor\frac Nn\rfloor\lfloor\frac Mn\rfloor

反演得到:
F(n)=ndμ(dn)D(d)F(n)=\sum_{n|d}\mu(\lfloor\frac dn\rfloor)D(d)

F(1)=ndμ(dNnMn)F(1)=\sum_{n|d}\mu(\lfloor d\rfloor\lfloor\frac Nn\rfloor\lfloor\frac Mn\rfloor)
這個過程再數學分塊求解就好了。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 50005;

int miu[maxn],v[maxn];

void prime() {
    for(int i = 1;i < maxn;i++) miu[i] = 1,v[i] = 0;
    for(int i = 2;i < maxn;i++) {
        if(v[i])continue;
        miu[i] = -1;
        for(int j = 2 * i;j < maxn;j += i) {
            v[j] = 1;
            if((j / i) % i == 0) miu[j] = 0;
            else miu[j] *= -1;
        }
    }
    for(int i = 1;i < maxn;i++) miu[i] += miu[i - 1];
}

void solve() {
    int a,b,k;scanf("%d%d%d",&a,&b,&k);
    int ans = 0;
    a /= k;b /= k;
    if(a > b) swap(a,b);
    for(int i = 1,j;i <= a;i = j + 1) { //除法分塊
        j = min(a / (a / i),b / (b / i));
        ans += (miu[j] - miu[i - 1]) * (a / i) * (b / i);
    }
    printf("%d\n",ans);
}

int main() {
    prime();
    int T;scanf("%d",&T);
    while(T--) {
        solve();
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章