[BZOJ 2301] Problem b【莫比烏斯反演/容斥原理/分塊】

[Description] 有n個詢問(n≤50000),每個詢問有五個整數a,b,c,d,k,求有多少個數對(x,y)滿足a≤x≤b,c≤y≤d,且gcd(x,y)=k.(a≤b≤50000,c≤d≤50000,k≤50000)

[Solution]
我們發現,計算一個數x在某個閉區間[a,b]內的因數數量並不是很方便,可以轉化爲x在區間[1,b]的因數的數量-x在區間[1,a-1]的因數的數量(因爲求[1,Z]比較好求)。

原問題是,對於所有x∈[a,b],y∈[c,d],求所有gcd(x,y)=k的點對的數量。
根據容斥原理,問題轉化爲,計算:
所有x∈[1,b],y∈[1,d],所有gcd(x,y)=k的點對的數量
-所有x∈[1,a-1],y∈[1,d],所有gcd(x,y)=k的點對的數量
-所有x∈[1,b],y∈[1,c-1],所有gcd(x,y)=k的點對的數量
+所有x∈[1,a-1],y∈[1,c-1],所有gcd(x,y)=k的點對的數量

所以怎麼求解任意x∈[1,n],y∈[1,m],所有gcd(x,y)=k的點對的數量呢?

設f(k)=任意x∈[1,n],y∈[1,m],所有gcd(x,y)=k的點對的數量,我們發現並不好求,於是再設F(k)=任意x∈[1,n],y∈[1,m],所有滿足k整除gcd(x,y)的點對的數量
因爲F值比f值容易求,所以我們這時可以運用莫比烏斯反演了。

容易發現,只要是k的倍數的f值都對F(k)有貢獻,即這裏寫圖片描述即可以推出這裏寫圖片描述

我們可以知道F(k)=floor(n/k)*floor(m/k),即n內有n/k個數是k的倍數,m內有n/k個數是k的倍數,他們兩兩之間的最小公因數一定包含k。

於是這裏寫圖片描述

這樣我們就可以在線性時間求出f的值了。
然而我們發現,每個詢問的需要計算四個f,複雜度依然達到平方級,仍需優化。

還有哪裏可以優化呢?
我們發現,其實有一些k,他們的floor(n/k)*floor(m/k)是一樣的,我們還要對他們分別求和,拉慢了運行速度。

因爲不同floor(n/k)floor(m/k)值最多2 (sqrt(n)+sqrt(m))個(證明如下)

可以發現,floor(n/k)的值最多2*sqrt(n)個(小於sqrt(n)的因數x對應一個大於sqrt(n)的因數n/x),我們將floor(n/k)值相等的k在數軸上分到一個區間,(如下圖),則最多2*sqrt(n)個區間,相同的,floor(m/k)值相等的k最多2*sqrt(m)個區間,以n=6,m=8爲例。

這裏寫圖片描述

就是說,floor(n/k)有2*sqrt(n/k)取值,floor(n/k)有3*sqrt(n/k)取值,它們對應起來的取值,就是劃分的2*(sqrt(n/k)+sqrt(m/k))區間的區間。

這裏寫圖片描述

且floor(n/k)*floor(m/k)值相同的k顯然是連續的,我們可以對μ求一下前綴和,對於值相同的k直接乘上他們的μ值和即可,這樣就可以將複雜度優化到O(n *sqrt(max(b,d)))了。

然而我們怎麼找到每個區間的起點和終點呢?我們發現,m/k可以找到floor值爲k的區間的末尾值,例如第一個數軸中6/floor(6/5)可以找到5所在區間的末尾6,第二個數軸中8/floor(8/5)可以找到5所在區間的末尾8,這樣,區間起點爲i,這個區間末尾就是min(floor(n/(n/i)),floor(m/(m/i))).

其實還可以更優化,求所有x∈[a,b],y∈[c,d],求所有gcd(x,y)=k的點對的數量可轉化爲x∈[a/k,b/k],y∈[c/k,d/k],求所有gcd(x,y)=1的點對的數量,只要在程序里加一句話就可以提速50%了,留給讀者自己思考。

之後的事就非常愉快辣!

[Code]

#include<cstdio>
#include<algorithm>
using namespace std;

const int maxn=50000+10;

bool form[maxn];
int prime[maxn],mu[maxn]={0,1},sum[maxn],cnt;
void MakeMuList(){
    for(int i=2;i<maxn;i++){
        if(!form[i]) prime[cnt++]=i,mu[i]=-1;
        for(int j=0;prime[j]*i<maxn;j++){
            form[prime[j]*i]=true;
            if(!(i%prime[j])){
                mu[prime[j]*i]=0;
                break;
            }
            mu[prime[j]*i]=-mu[i];
        }
    }
    for(int i=1;i<maxn;i++) sum[i]=sum[i-1]+mu[i];
}

int f(int n,int m,int k){
    int ans=0;
    if(n>m) swap(n,m);
    for(int i=1,last;i<=n;i=last+1){
        last=min(n/(n/i),m/(m/i));
        ans+=(sum[last]-sum[i-1])*(m/(i*k))*(n/(i*k));
    }
    return ans;
}

int main(){
    MakeMuList();
    int kase; scanf("%d",&kase);
    while(kase--){
        int a,b,c,d,k; scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
        printf("%d\n",f(b,d,k)-f(a-1,d,k)-f(b,c-1,k)+f(a-1,c-1,k));
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章