BZOJ 2301 Problem b(莫比乌斯反演+容斥原理)

2301: [HAOI2011]Problem b

Time Limit: 50 Sec  Memory Limit: 256 MB
Submit: 7205  Solved: 3425
[Submit][Status][Discuss]

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

HINT

100%的数据满足:1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000

 

 

要求两个区间范围内的数字的gcd为k的对个数。这种两个数字范围的题目按照套路都是要用容斥原理拆分成四个子问题。每个子问题形式为cal(a,b,k),即两个数字的取值范围分别是[1,a]和[1,b],然后去求解个数。

我们考虑把询问写成gcd(a,b,k)的形式,那么显然有gcd(a,b,k)==gcd(a/l,b/k,1)。我们令f(i)=gcd(a,b,i),即f(i)为gcd(a,b)==i这样的(a,b)个数,然后令F(i)为i | gcd(a,b)这样的(a,b)的对数。显然有 F(i)=\lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{i}\rfloor 。根据定义,我们可以写出以下式子:

                                                                \large F(i)=\sum_{i|d}f(i)

即我们要求的答案求和的结果是F(i)。我们发现F(i)的通项公式已经给出,但是这个f(i)却不是那么的好求。于是,我们利用莫比乌斯反演定理,利用F(i)反演求出f(i)。莫比乌斯反演了解一下:莫比乌斯反演的两种形式及其证明

                  \large F(i)=\sum_{i|d}f(d)=>f(i)=\sum_{i|d}\mu(\frac{d}{i})F(d)=\sum_{i|d}\mu(\frac{d}{i})\lfloor\frac{n}{d}\rfloor\lfloor\frac{m}{d}\rfloor

推出f(i)表达式之后,我们就可以对gcd(a,b,k)求解了。我们首先把问题变成gcd(a/k,b/k,1),这样对应的就是f(1)。

                                                    \large f(1)=\sum_{d=1}^{min(n,m)}\mu(d)\lfloor{\frac n d}\rfloor\lfloor{\frac m d}\rfloor

如此直接枚举d求解的话,一次询问复杂度为O(N),总共q次询问就是O(Nq),然而这个复杂度显然是不行的。接着,我们注意到对于一个数字n,其对应的 \large \lfloor{\frac n d}\rfloor 的取值最多只有\large 2sqrt(n),于是我们可以利用这个性质进行分段优化。举个例子说,对于n=100,d=34,此时 \large \lfloor{\frac n d}\rfloor 的值等于2,而当d循环到50的时候,取值还是2,这意味着这一段我就可以一起统计。预处理莫比乌斯函数的前缀和,然后对于同一段的用这一段的莫比乌斯函数之和乘以F(d)。这样,总的复杂度就可以优化到\large O(2\sqrt{n}+2\sqrt{m})。具体见代码:

#include<bits/stdc++.h>
#define IO ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define LL long long
#define N 50010

using namespace std;

int s[N],mu[N],p[N];
bool isp[N];

void init()
{
    int sz=0;
    s[1]=mu[1]=1; 
    memset(isp,1,sizeof(isp));
    for(int i=2;i<N;i++)
    {
        if(isp[i]) p[++sz]=i,mu[i]=-1;
        for(int j=1;j<=sz&&(LL)i*p[j]<N;j++)
        {
            isp[i*p[j]]=0;
            if(i%p[j]==0)
            {
                mu[p[j]*i]=0; break;
            } else mu[p[j]*i]=-mu[i];
        }
        s[i]=s[i-1]+mu[i];
    }
}

int cal(int n,int m)
{
    int res=0,last;
    for(int i=1;i<=n&&i<=m;i=last+1)
    {
        last=min(n/(n/i),m/(m/i));
        res+=(s[last]-s[i-1])*(n/i)*(m/i);
    }
    return res;
}

int main()
{
    int a,b,c,d,k,n;
    IO; init(); cin>>n;
    while(n--)
    {
        cin>>a>>b>>c>>d>>k;
        a--; c--; b/=k; d/=k; a/=k; c/=k;
        cout<<cal(b,d)-cal(a,d)-cal(b,c)+cal(a,c)<<endl;
    }
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值