BZOJ 2301 [HAOI2011]Problem b (莫比乌斯反演)

Description

对于给出的 $n$ 个询问,每次求有多少个数对 $(x,y)$ ,满足 $a≤x≤b,c≤y≤d$ ,且 $\gcd(x,y) = k$ ,$\gcd(x,y)$ 函数为 $x$ 和 $y$ 的最大公约数。

 

Input

第一行一个整数 $n$ ,接下来 $n$ 行每行五个整数,分别表示 $a、b、c、d、k$

 

Output

共 $n$ 行,每行一个整数表示满足要求的数对 $(x,y)$ 的个数

 

Sample Input

2
2 5 1 5 1
1 5 1 5 2

 

Sample Output

14
3

 

思路

首先我们可以利用容斥原理将一个询问分解为四个,每次询问有多少个数对 $(x,y)$ 满足 $1<=x<=n,1<=y<=m$ 且 $\gcd(x,y)=k$ 。

然后问题便等价于询问有多少个数对 $(x,y)$ 满足 $1<=x<=\lfloor\frac{n}{k}\rfloor,1<=y<=\lfloor\frac{m}{k}\rfloor$ 且 $x$ 与 $y$ 互质。

PS:这道题目的时间限制有 50s ,不过也千万别想枚举过掉它,因为 $O(n^3)$ 的复杂度在 $n=1e5$ 的情况下可能要跑好久好久。

我们考虑使用莫比乌斯反演

因为之前的结论,我们可以令 $f(i)$ 为 $1<=x<=n,1<=y<=m$ 且 $\gcd(x,y)=i$ 的数对 $(x,y)$ 的个数, $F(i)$ 为 $1<=x<=n,1<=y<=m$ 且 $i|\gcd(x,y)$ 的数对 $(x,y)$ 的个数,满足 $F(i)=\sum_{i|d}f(d)$ 。

显然,我们有 $F(i)=\lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{i}\rfloor$

反演之后即 $f(i)=\sum_{i|d}μ(\frac{d}{i})F(d)=\sum_{i|d}μ(\frac{d}{i})\lfloor\frac{n}{d}\rfloor\lfloor\frac{m}{d}\rfloor$

枚举原题中 $k$ 的倍数,我们就可以在 $O(n)$ 的时间处理每一个询问了,不过 $O(n)$ 还不能胜任本题的数据范围,于是我们考虑进一步优化。

我们发现,对于其中连续的 $s$ 项,有可能有 $\lfloor\frac{n}{d}\rfloor=\lfloor\frac{n}{d+s}\rfloor$ ,那么对于这 $s$ 项的贡献,我们可以直接得出,即 $(Sum_{d+s}-Sum_{d-1})×\frac{n}{d}×\frac{m}{d}$ ,其中 $Sum_i$ 代表莫比乌斯函数的前 $i$ 项和。

观察式子,我们发现 $\lfloor\frac{n}{d}\rfloor$ 最多有 $2\sqrt{n}$ 个取值,于是 $\lfloor\frac{n}{d}\rfloor\lfloor\frac{m}{d}\rfloor$ 至多有 $2(\sqrt{n}+\sqrt{m})$ 个取值,所以我们只需要枚举这 $2(\sqrt{n}+\sqrt{m})$ 个取值就可以了。

PS:关于如何求出二元组 $(\frac{n}{x},\frac{m}{x})$ 取值范围相同的一段:对于当前点 $x$ ,其向右延伸最远为 $\min(\cfrac{n}{\cfrac{n}{x}},\cfrac{m}{\cfrac{m}{x}})$ ,因为 $\frac{n}{x}$ 为下界,那么 $\cfrac{n}{\cfrac{n}{x}}$ 就是上界咯。

 

AC 代码

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<stdlib.h>
#include<iostream>
#include<queue>
#include<vector>
#include<map>
#include<cmath>
#include<algorithm>
using namespace std;

const int maxn = 1e5+10;
typedef long long LL;
bool check[maxn];
int prime[maxn];
int mu[maxn];
LL sum[maxn];

void Moblus()
{
    memset(check,false,sizeof(check));
    mu[1]=1;
    int tot=0;
    for(int i=2; i<maxn; i++)
    {
        if(!check[i])
        {
            prime[tot++]=i;
            mu[i]=-1;
        }
        for(int j=0; j<tot && i*prime[j]<maxn; j++)
        {
            int num=i*prime[j];
            check[num]=true;
            if(i%prime[j]==0)
            {
                mu[num]=0;
                break;
            }
            else
            {
                mu[num]=-mu[i];
            }
        }
    }
}

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

int main()
{
    Moblus();
    for(int i=1; i<maxn; i++)
        sum[i]=sum[i-1]+mu[i];
    int a,b,c,d,k,T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
        LL ans=solve(b/k,d/k)-solve((a-1)/k,d/k)-solve(b/k,(c-1)/k)+solve((a-1)/k,(c-1)/k);
        printf("%lld\n",ans);
    }
    return 0;
}