【BZOJ】2818 Gcd

Gcd


题目链接


题目大意

    给你一个数n, 1<=x,y<=n 现在让你求gcd(x,y)=p,p为质数的x,y共有多少对。


题解

莫比乌斯反演

    看了很多资料,对的错的都看了不少…..总算有了一些头绪。
    首先莫比乌斯反演有两种形式,一般的,我们用

F(x)=d|xf(d)                   f(x)=d|xμ(d)F(xd)

    还有一种形式
F(x)=x|df(d)                   f(x)=x|dμ(dx)F(d)

    在这里我们用第二种形式:
f(d)gcd(x,y)=d(x,y)F(d)d|gcd(x,y)(x,y)

    根据反演,有
f(x)=x|dμ(dx)F(d)

    根据F(x)的定义 F(x)=(Nd)2
    所以我们有
f(x)=x|dμ(dx)(Nd)2

    到这里,我们可以写出最终的结果:
ans=xnf(x)=xx|dμ(dx)(Nd)2

    为了简化计算,我们考虑先枚举d,再枚举x:
ans=d=2n(Nd)2x|dxμ(dx)

    直接枚举时间肯定不够,不过可以看到我们只要求出后面的一项 x|dxμ(dx) 我们就可以在线性时间内求出ans了。所以在这里,我们设:
g(x)=p|xμ(xp)        (p)

    对于g(x),我们看到 g(x) μ(x) 有关,可以想到是否可以在筛出 μ(x) 的时候同时筛出g(x)。在这里我们分类讨论:
g(iprime(j))=p|iprime(j)μ(iprime(j)p)

    首先,如果 prime(j)|i
iprime(j)prime(j)pμ(i)g(iprime(j))=0

g(iprime(j))=μ(i)         ( prime(j)|i )(*)

    如果prime(j)不能整除i的话:
    首先考虑 prime(j)!=p
g(iprime(j))=p|iprime(j)μ(ip)μ( prime(j) )

    因为这里我们是在枚举p算和,所以 μ( prime(j) ) 可以提前,得到
g(iprime(j))=μ( prime(j) )p|iprime(j)μ(ip)

    可以看到 μ( prime(j) ) 为-1,而后面就是 g(i) 所以最终求和,所以此时
g(iprime(j))=g(i)

    而如果 prime(j)=p 的话,消掉又变成 μ(i) 了,所以在prime(j)不能整除i的情况下:
g(iprime(j))=μ(i)g(i)(**)

    综上所述
g(iprime(j))={μ(i)   prime(j)|iμ(i)g(i)   prime(j)i

    到这里,问题圆满解决,最后求出g(i)的前缀和然后分块求出最终结果就行了。


代码

#include <iostream>
#include <cstring>
#include <cstdio>
#define LL long long
#define maxn 10000005

using namespace std;

int n,p[maxn],g[maxn],mu[maxn],num;
bool vis[maxn];

void setup(int high)
{
    memset(g,0,sizeof(g));
    mu[1]=1; num=0;
    for (int i=2;i<=high;i++)
    {
        if (!vis[i])
        {
            vis[i]=1; p[num++]=i;
            mu[i]=-1;
            g[i]=1;
        }
        for (int j=0;j<num && i*p[j]<=high;j++)
        {
            vis[i*p[j]]=1;
            if (i%p[j])
            {
                mu[i*p[j]]=-mu[i];
                g[i*p[j]]=mu[i]-g[i];
            }
            else
            {
                mu[i*p[j]]=0;
                g[i*p[j]]=mu[i];
                break;
            }
        }
    }
    for (int i=1;i<high;i++) g[i]+=g[i-1];
}

int main()
{
    setup(maxn-5);
    scanf("%d",&n);
    int last=0;
    LL ans=0;
    for (int T=2;T<n;T=last+1)
    {
        last=(n/(n/T));
        ans+=(LL) (g[last]-g[T-1])*(n/T)*(n/T);
    }
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值