[BZOJ3994][SDOI2015]约数个数和

[SDOI2015]约数个数和

Description
设d(x)为x的约数个数,给定N、M,求 ni=1mj=1d(ij)
Input
输入文件包含多组测试数据。
第一行,一个整数T,表示测试数据的组数。
接下来的T行,每行两个整数N、M。
Output
T行,每行一个整数,表示你所求的答案。
Sample Input
2
7 4
5 6
Sample Output
110
121
HINT
1<=N, M<=50000
1<=T<=50000

Solution
有一个结论:
ni=1mj=1d(ij)=ni=1mj=1nimj[gcd(i,j)==1]
然后随便反演就行
【有谁能告诉我这结论是怎么想到的。。

Code

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int MaxN = 50010;

int n,m;
LL miu[MaxN],d[MaxN],c[MaxN];
int prime[MaxN],flag[MaxN],cnt;

inline void kyuu(){
    miu[1]=d[1]=1;
    for(int i=2;i<=50000;i++){
        if(!flag[i]) prime[++cnt]=i,miu[i]=-1,d[i]=2,c[i]=1;
        for(int j=1;j<=cnt && prime[j]*i<=50000;j++){
            flag[i*prime[j]]=1;
            if(i%prime[j]==0){
                miu[i*prime[j]]=0;
                d[i*prime[j]]=d[i]/(c[i]+1)*(c[i]+2);
                c[prime[j]*i]=c[i]+1;
                break;
            }
            miu[i*prime[j]]=-miu[i];
            d[i*prime[j]]=d[i]*2;
            c[prime[j]*i]=1;
        }
    }
    for(int i=2;i<=50000;i++) miu[i]+=miu[i-1],d[i]+=d[i-1];
}

int main()
{
    int T=0;
    kyuu();
    for(scanf("%d",&T);T--;){
        scanf("%d%d",&n,&m);
        LL res=0;
        for(int i=1,t;i<=n && i<=m;i=t+1)
            t=min(n/(n/i),m/(m/i)),res+=(miu[t]-miu[i-1])*d[n/i]*d[m/i];
        printf("%lld\n",res);
    }
    return 0;
}

你可能感兴趣的:(数学,莫比乌斯反演)