洛谷 P3327 [SDOI2015]约数个数和 莫比乌斯反演

题目描述

设d(x)为x的约数个数,给定N、M,求
∑ i = 1 n ∑ j = 1 m d ( i ∗ j ) \sum_{i=1}^n\sum_{j=1}^{m}d(i*j) i=1nj=1md(ij)
其中d(x)表示约数个数。

输入输出格式

输入格式:
输入文件包含多组测试数据。第一行,一个整数T,表示测试数据的组数。接下来的T行,每行两个整数N、M。

输出格式:
T行,每行一个整数,表示你所求的答案。

输入输出样例

输入样例#1:
2
7 4
5 6
输出样例#1:
110
121
说明

1<=N, M<=50000

1<=T<=50000

分析:
一个很神奇的结论,
d ( i ∗ j ) = ∑ x ∣ i ∑ y ∣ j [ g c d ( x , y ) = = 1 ] d(i*j)=\sum_{x|i}\sum_{y|j}[gcd(x,y)==1] d(ij)=xiyj[gcd(x,y)==1]
然后套路一波。

代码:

#include 
#include 
#include 
#define LL long long

const int maxn=50007;

using namespace std;

int prime[maxn],not_prime[maxn],mul[maxn],sum[maxn];
LL g[maxn];
int test,n,m,cnt;

void getmul(int n)
{
    mul[1]=1;
    for (int i=2;i<=n;i++)
    {
        if (!not_prime[i])
        {
            prime[++cnt]=i;
            mul[i]=-1;
        }
        for (int j=1;j<=cnt;j++)
        {
            if (i*prime[j]>n) break;
            not_prime[i*prime[j]]=1;
            if (i%prime[j]==0)
            {
                mul[i*prime[j]]=0;
                break;
            }
            mul[i*prime[j]]=-mul[i];
        }
    }
    for (int i=1;i<=n;i++) sum[i]=sum[i-1]+mul[i];
    for (LL i=1;i<=n;i++)
    {
        for (LL j=1,last;j<=i;j=last+1)
        {
            last=i/(i/j);
            g[i]+=(last-j+1)*(i/j);
        }
    }
}

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

int main()
{
    getmul(maxn);	
    scanf("%d",&test);
    while (test--)
    {
        scanf("%d%d",&n,&m);
        printf("%lld\n",calc(n,m));
    }
}

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