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

Description

对于给出的 n 个询问,每次求有多少个数对 (x,y) ,满足 axb,cyd ,且 gcd(x,y)=k gcd(x,y) 函数为 x y 的最大公约数。

 

Input

第一行一个整数 n ,接下来 n 行每行五个整数,分别表示 abcdk

 

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<=nk,1<=y<=mk x y 互质。

PS:这道题目的时间限制有 50s ,不过也千万别想枚举过掉它,因为 O(n3) 的复杂度在 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)=i|df(d)

显然,我们有 F(i)=nimi

反演之后即 f(i)=i|dμ(di)F(d)=i|dμ(di)ndmd

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

我们发现,对于其中连续的 s 项,有可能有 nd=nd+s ,那么对于这 s 项的贡献,我们可以直接得出,即 (Sumd+sSumd1)×nd×md ,其中 Sumi 代表莫比乌斯函数的前 i 项和。

观察式子,我们发现 nd 最多有 2n 个取值,于是 ndmd 至多有 2(n+m) 个取值,所以我们只需要枚举这 2(n+m) 个取值就可以了。

PS:关于如何求出二元组 (nx,mx) 取值范围相同的一段:对于当前点 x ,其向右延伸最远为 min(nnx,mmx) ,因为 nx 为下界,那么 nnx 就是上界咯。

 

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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值