hdu1695 GCD

链接:http://acm.hdu.edu.cn/showproblem.php?pid=1695
题意:有两个区间[a,b]和[c,d]求这两个区间中gcd(i,j)==k的对数, i ∈ \in [a,b],j ∈ \in [c,d]。
不能重复,数对(i,j)和(j,i)视为相同。

另一道差不多题目的题解参考:https://blog.csdn.net/qq_40942372/article/details/88656852

这道题还特别说了是区间[1,a]和[1,b]型的,不同的还有求的是gcd==k以及不能重复。

直接到这一步 f ( n ) = ∑ ( u ( d / n ) ∗ g ( d ) ) f(n)=∑(u(d/n)∗g(d)) f(n)=(u(d/n)g(d))n|d
这里n=k,所以 求 f ( k ) = ∑ ( u ( d / k ) ∗ g ( d ) ) f(k)=\sum(u(d/k)*g(d)) f(k)=(u(d/k)g(d))
g ( d ) g(d) g(d)也一样,还是等于 ( a / d ) ∗ ( b / d ) (a/d)*(b/d) (a/d)(b/d)

g(d)为gcd(i,j)%d==0的对数,也就是说i和j都是d的倍数,那么就看i有几个j有几个,相乘就是g(d)了
i和j的个数很显然就是上界/d,所以g(d)=(a/d)∗(b/d)最后f(k)=∑(u(d/k)∗(a/d)∗(b/d)),在范围内枚举d(d为k的倍数),当然也可以直接把上界全部除k,这样的话f(k)=∑(u(d/k)∗(a/d)∗(b/d))就变成了f(1)=∑(u(d)∗(a/k/d)∗(b/k/d)),枚举可以方便一点。

如何处理重复:稍微找几个例子如 1 3 1 5 1
(1,1)(1,2)(1,3)(1,4)(1,5)
(2,1)(2,3)(2,5)
(3,1)(3,2)

重复的数字都是包含在上界较小的那个区间中的,不会超过这个区间的右端点。想一想,如果在两个小区间求gcd(i,j)==k的结果会是什么,不就是这些重复的再加上一个么,向上面例子的话就是
(1,1)(1,2)(1,3)
(2,1)(2,3)
(3,1)(3,2)
重复的数量就是(两个小区间求gcd(i,j)==k的结果-1)/2
最终的答案就是之前算出来的减去这部分

优化还是一样的分块优化,可以看那篇

小坑点:k可以等于0,特判一下

参考代码:

#include
using namespace std;

typedef long long ll;

const int N=1e5+5;
const int maxn=1e5;
int mu[N],prime[N];
bool vis[N];
void work_mobius()
{
    mu[1]=1;
    int cnt=0;
    for(int i=2;i<=maxn;i++)
    {
        if(!vis[i])
        {
            prime[cnt++]=i;
            mu[i]=-1;
        }
        for(int j=0;j<cnt&&i*prime[j]<=maxn;j++)
        {
            vis[i*prime[j]]=true;
            if(i%prime[j]) mu[i*prime[j]]=-mu[i];
            else
            {
                mu[i*prime[j]]=0;
                break;
            }
        }
    }
    for(int i=2;i<=maxn;i++)
        mu[i]=mu[i-1]+mu[i];
}

ll solve(int b,int d){
    ll ret=0;
    for(int l=1,r=0;l<=min(b,d);l=r+1){
        r=min(b/(b/l),d/(d/l));
        ret+=1ll*(mu[r]-mu[l-1])*(b/l)*(d/l);
    }
    return ret;
}


int main(){
    work_mobius();
    int t;
    scanf("%d",&t);
    for(int ca=1;ca<=t;ca++){
        int a,b,c,d,k;
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
        if(k==0){
            printf("Case %d: 0\n",ca);
            continue;
        }
        if(b>d)swap(b,d);//这道题目可以不需要容斥,这样写即使左端点不是1也是对的
        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);
        ll duo=solve(b/k,b/k)-solve((a-1)/k,b/k)-solve(b/k,(a-1)/k)+solve((a-1)/k,(a-1)/k);
        printf("Case %d: %lld\n",ca,ans-duo/2);
    }
    return 0;
}

你可能感兴趣的:(数论)