BZOJ 3994 [SDOI2015]约数个数和 (莫比乌斯反演)

3994: [SDOI2015]约数个数和

Time Limit: 20 Sec   Memory Limit:128 MB
Submit: 239   Solved: 176
[ Submit][ Status][ Discuss]

Description

 设d(x)为x的约数个数,给定N、M,求  

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

Source

Round 1 感谢yts1999上传


题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3994


题目分析:和上一题类似,还是见公式:ni=1mj=1d(ij)=ni=1mj=1nimj[gcd(i,j)==1],

于是得到:


Ans=i=1nj=1md|i,d|jμ(d)n/im/j

变形得到

d=1min(n,m)μ(d)i=1n/dj=1m/dnidmjd 


继续变形

d=1min(n,m)μ(d)i=1n/dndij=1m/dmdj

i=1n/dndi=f(nd)

公式变成
d=1min(n,m)μ(d)f(nd)f(md)

现在的问题是怎样计算f的值,f[i]表示的是ik=1ik

,我们先考虑一个函数f'[i],设f'[i]为i的约数的个数,那么显然f'[i]为积性函数,我们可以用线性筛得到,仔细观察可以发现f[i] = f'[1] + f'[2] + ... + f'[i],比如f[6] = f'[1]+f'[2]+...+f'[6] = 1+2+2+3+2+4 = 6+3+2+1+1+1 = 14,下面简单解释一下线性筛是如何得到一个数约数的个数的,首先根据约数个数定理:对于一个大于1的正整数n可以分解质因数:n=p1^a1*p2^a2*p3^a3*…*pk^ak,则n的正约数有(a₁+1)(a₂+1)(a₃+1)…(ak+1)个,又我们知道线性筛筛质数每次筛的都是用最小的质因子去筛,因此我们可以记录这个最小质因子唯一分解后的次幂然后通过上面的公式求解,详细见程序注释。这样的话问题就解决了,同时把u和f都筛出来,剩下的分块求和即可

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N=50000+5;
const int mod=1073741824;
typedef long long ll;
int mb[N];
int pri[N],tot=0;
bool vis[N];
ll f[N];
int d[N];
int ff[N];
ll sum[N];
void init()
{
    memset(vis,false,sizeof vis);
    mb[1]=1;
    sum[0]=0;
    sum[1]=1;
    ff[1]=1;
    f[1]=1;
    for(int i=2;i<N;i++)
    {
        if(!vis[i]) {
            pri[tot++]=i;
            mb[i]=-1;
            d[i]=1;
            ff[i]=2;
        }
        for(int j=0;j<tot && i*pri[j]<N;j++)
        {
            vis[i*pri[j]]=true;
            if(i%pri[j]) {
                mb[i*pri[j]]=-mb[i];
                ff[i*pri[j]]=ff[i]*2;
                d[i*pri[j]]=1;
            }
            else {
                mb[i*pri[j]]=0;
                ff[i*pri[j]]=ff[i]/(d[i]+1)*(d[i]+2);
                d[i*pri[j]]=d[i]+1;
                break;
            }
        }
        sum[i]=sum[i-1]+mb[i];
        f[i]=f[i-1]+ff[i];
    }
}
int main()
{
    init();
    int T_T;
    scanf("%d",&T_T);
    while(T_T--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        ll ans=0;
        int up=min(n,m);
        for(int d=1,la=0;d<=up;d=la+1)
        {
            la=min(n/(n/d),m/(m/d));
            ans+=(sum[la]-sum[d-1])*f[n/d]*f[m/d];
        }
        printf("%lld\n",ans);
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/wust_zzwh/article/details/52166018