[BZOJ2671][莫比烏斯反演]Calc

題目大意:給出N ,求有多少組 (a,b) 使得 a+b|ab
我們首先令 d=gcd(a,b) , 則有 a=idb=jd
a+b|ab
=>d(i+j)|ij(d2)
=>(i+j)|ijd
又因爲 gcd(i,j)==1
所以 gcd(i,i+j)=gcd(j,i+j)=1
=>(i+j)|d
不妨設 d=(i+j)k ,所以 a=i(i+j)k,b=j(i+j)k
即我們的問題現在轉化爲求有多少對三元組 (i,j,k)
使得 i ( i + j ) k <= N && j ( i + j ) k <= N && gcd ( i , j ) == 1
若是枚舉 i<j 統計 k 的數量:sigma(N/(j(i+j))) ,時間複雜度 O(N)
對於 N<=2311 我們是必然超時的,那麼怎麼優化呢?
我們會發現 N/(j(i+j)) 是整除,也就是說,
有一部分 i,j 值的 k 的數量是相同的,我們可以算出,
相同的 k 的數量最多有 2N 種。
於是這裏有一個神奇的做法:我們不妨枚舉 j(j<=N)
初始化 i=1 , 再令 last=N/(N/j/(i+j))/jj
那麼我們可以說 [i,last] 這個區間中的 N/(j(i+j)) 是相同的。
至於上面說的神奇的方法,
可以理解爲 本來 N 除以 j(i+j) 有餘數可以使得在 jN/(j(i+j)) 不變的情況下,
i 還有增長的區間,除了兩次後,i 就變成了 jN/(j(i+j)) 不變時的的最大值。
這時下個區間就是[last+1,last]
至於在當前區間內有多少個 i 使得 gcd(i,j)==1 就是莫比烏斯反演的基本內容了

具體細節可以看代碼:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;

#define MAXN 50000
#define MAXM
#define INF 0x3f3f3f3f
typedef long long int LL;

void Read(LL &x){
    x=0;char c=getchar();bool flag=0;
    while(c<'0'||'9'<c){if(c=='-')flag=1;c=getchar();}
    while('0'<=c&&c<='9'){x=x*10+c-'0';c=getchar();}
    if(flag)x=-x;
}

int u[MAXN+10];
bool isprime[MAXN+10];
int prime[MAXN+10],Cnt;

void init(int n){
    memset(isprime,1,sizeof(isprime));
    Cnt=0;

    n=sqrt(n);
    u[1]=1;
    for(int i=2;i<=n;++i){
        if(isprime[i]){
            prime[++Cnt]=i;
            u[i]=-1;
        }

        for(int j=1;j<=Cnt&&i*prime[j]<=n;++j){
            isprime[i*prime[j]]=0;
            if(i%prime[j])
                u[i*prime[j]]=-u[i];
            else{
                u[i*prime[j]]=0;
                break;
            }
        }
    }
}

int num[MAXN+10],top;
void Get_Divisor(LL n){
    LL i;
    top=0;
    for(i=1;i*i<n;i++)
        if(n%i==0)num[++top]=i,num[++top]=n/i;
    if(i*i==n)num[++top]=i;
    sort(num+1,num+top+1);
}

int main(){
    LL N;
    Read(N);
    init(N);

    LL i,j,k,last=0;
    LL cnt=0;
    LL ans=0;
    for(j=1;j*(j+1)<=N;++j){
        Get_Divisor(j);
        for(i=1;i<j&&j*(i+j)<=N;i=last+1){
            last=min(N/(N/j/(i+j))/j-j,j-1);
            cnt=0;
            for(k=1;num[k]<=last;++k)
                cnt+=u[num[k]]*(last/num[k]-(i-1)/num[k]);
            ans+=N/j/(i+j)*cnt;
        }
    }

    printf("%lld\n",ans);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章