gym101982 B Coprime Integers (莫比乌斯)

博客围绕两个区间[a,b]和[c,d]求gcd(i,j)==1的对数展开。先介绍[1,a]和[1,b]区间求该对数的方法,运用反演公式得出f(1)表达式。再利用容斥原理解决原问题,最后提出优化方案,将时间复杂度从O(N)降为O(√N)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链接:http://codeforces.com/gym/101982/attachments
题意:有两个区间[a,b]和[c,d],求gcd(i,j)==1的对数,i∈\in[a,b],j∈\in[c,d]

和那种[1,a]和[1,b]两个区间求gcd(i,j)==1的对数的题目很类似。和那种[1,a]和[1,b]两个区间求gcd(i,j)==1的对数的题目很类似。[1a][1b]gcd(ij)==1
在[1,a]和[1,b]两个区间求gcd(i,j)==1的问题中,在[1,a]和[1,b]两个区间求gcd(i,j)==1的问题中,[1a][1b]gcd(ij)==1
定义f(d)为gcd(i,j)==d的对数定义f(d)为gcd(i,j)==d的对数f(d)gcd(i,j)==d
定义g(n)为gcd(i,j)%n==0的对数定义g(n)为gcd(i,j)\%n==0的对数g(n)gcd(i,j)%n==0
这样就有了一个公式:g(n)=∑(f(d))这样就有了一个公式:g(n)=\sum(f(d))g(n)=(f(d)) n|d
根据反演公式:F(n)=∑(U(d/n)∗G(d))根据反演公式:F(n)=\sum(U(d/n)*G(d)):F(n)=(U(d/n)G(d))n|d
那么f(n)=∑(u(d/n)∗g(d))那么f(n)=\sum(u(d/n)*g(d))f(n)=(u(d/n)g(d))n|d
求gcd==1,也就是求f(1)=∑(u(d)∗g(d))求gcd==1,也就是求f(1)=\sum(u(d)*g(d))gcd==1f(1)=(u(d)g(d))1|d
反演的本质是一个未知的函数由一个已知的函数通过一些变化来得到,所以需要观察g(d)反演的本质是一个未知的函数由一个已知的函数通过一些变化来得到,所以需要观察g(d)g(d)
g(d)为gcd(i,j)%d==0的对数,也就是说i和j都是d的倍数,那么就看i有几个j有几个,相乘就是g(d)了g(d)为gcd(i,j)\%d==0的对数,也就是说i和j都是d的倍数,那么就看i有几个j有几个,相乘就是g(d)了g(d)gcd(i,j)%d==0ijdijg(d)
i和j的个数很显然就是上界/d,所以g(d)=(a/d)∗(b/d)最后f(1)=∑(u(d)∗(a/d)∗(b/d))i和j的个数很显然就是上界/d,所以g(d)=(a/d)*(b/d) 最后f(1)=∑(u(d)∗(a/d)*(b/d))ij/dg(d)=(a/d)(b/d)f(1)=(u(d)(a/d)(b/d))1|d

到这里右边全部都是已知的,枚举下可取范围内的d(也就是原来n的倍数,这里n是1)
再回过来看看这道题,可以利用容斥原理,先求出[1,b]和[1,d],再减去[1,a-1]和[1,d]以及[1,b]和[1,c-1],最后加上多减的部分[1,a-1]和[1,c-1]。是不是有点像二维的前缀和求一个矩形的求法。

最后考虑下优化,这里求的gcd是1,也就是说范围内的每个数字都要枚举。f(1)=∑(u(d)∗(a/d)∗(b/d))f(1)=∑(u(d)∗(a/d)∗(b/d))f(1)=(u(d)(a/d)(b/d)),观察发现当上界很大时,会有很长的一段区间[l,r]满足a/x的值相同,x∈\in[l,r],这里(a/d)∗(b/d)的值是有两个区间合成的

不同颜色表示不同的值,这样合成的不同值有三个,这个也是代码中左边界取min的意义。
如果现在有一段区间的(a/d)∗(b/d)值相同了,简单表示x=(a/d)∗(b/d),那么这段区间的最终贡献为:x*∑u(l->r),其中∑u(l->r)用前缀和维护下就可以O1得到,这个优化差不多是降ON为O根号N

参考代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N=1e7+5;
const int maxn=1e7;
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));//不理解的看看上面的图 (b/l)是第一个区间这段的值,b/(b/l)就变成这一段最右边的那个位置
        ret+=1ll*(mu[r]-mu[l-1])*(b/l)*(d/l);
    }
    return ret;
}


int main(){
    work_mobius();
    int a,b,c,d;
    scanf("%d%d%d%d",&a,&b,&c,&d);
    printf("%lld\n",solve(b,d)-solve(a-1,d)-solve(b,c-1)+solve(a-1,c-1));
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值