bzoj 2820-莫比乌斯函数反演+分块优化 +换元优化

BZOJ2820 YGYGCD


题目大意:求有多少数对(x,y)(1<=x<=n,1<=y<=m)满足gcd(x,y)为质数

T = 10000

N, M <= 10000000


参考popoqqqPPT:


首先,根据http://blog.csdn.net/viphong/article/details/52451540

 

f(x)表示gcd(i,j)==x的ij对数,F(x)是包含f(x)的



那么对于一个质数其贡献为 f(i)= 


直接计算,光枚举p就已经TLE了。


我们可以发现,p是所有的质数,f(p)枚举了每个质数的所有倍数,这其实不就是把所有的数都枚举了吗:


也就是从 

变成了


而我们发现,这个跟又跟那个分块优化的套路非常相似了

我们只需要预处理出 的前缀和,就可以sqrt(n)求到答案了



这个东西什么意思? 就是对于一个d, 把mu(d的约数)求和。

而这个我们可以预处理,枚举所有质数,去更新这个质数的所有倍数。

假设我们枚举的是n个数,那么每一个数的倍数有n/i个,一共枚举的数量就是=n* ,调和级数嘛,也就是n*lgn咯,似乎可以证明是均摊logn,而质数的个数约为O(n/lgn),因此

枚举所有质数的倍数的复杂度约为,o(logn*n/logn)=o(n)


再求个前缀和,所以预处理这一部分的复杂度是o(n)


综上复杂度为sqrt(n)




参考代码:



#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long  ll;
const ll p =1000000007;
const int  N=10000000;
bool is_prime[N+500];
int prime[N+50];
int mu[N+50];
ll sum[N+50];
ll tot;

void Moblus()
{
    tot = 0;
    mu[1] = 1;
    for(ll i = 2; i < N; i++)
    {
        if(!is_prime[i])
        {
            prime[tot++] = i;
            mu[i] = -1;
        }
        for(ll j = 0; j < tot && i*prime[j] < N; j++)
        {
            is_prime[i*prime[j]] = 1;
            if(i % prime[j])
            {
                mu[i*prime[j]] = -mu[i];
            }
            else
            {
                mu[i*prime[j]] = 0;
                break;
            }
        }
    }
}

ll ud[N+50];
void pre()
{
     for (int i=0;i<tot;i++)
    {
        for (ll j=prime[i];j<=N;j+=prime[i])
        {
            ud[j]+=mu[j/prime[i]];
        }
    }
}

//找[1,n],[1,m]内gcd(i,j)为质数的对数
ll solve(ll n,ll m )
{
    if (n>m)swap(n,m);
    ll ret=0;
    for (int i=1,last;i<=n;i=last+1)
    {
        last=min(n/(n/i),m/(m/i));
        ret+=(sum[last]-sum[i-1])*(n/i)*(m/i);
    }
    return ret;
}
int main()
{
    Moblus();
    pre();
    for (int i=1;i<N;i++)
        sum[i]=sum[i-1]+ud[i];
    freopen("YYnoGCD.in","r",stdin);
    freopen("YYnoGCD.out","w",stdout);
    int t;cin>>t;
    while(t--)
    {
        ll n,m;
        scanf("%lld%lld",&n,&m);
        ll ans=solve(n,m);

        printf("%lld\n",ans);
    }


    return 0;
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值