[BZOJ2301][HAOI2011]Problem b(莫比乌斯反演)

=== ===

这里放传送门

=== ===

题解

感觉自己在懵(fei)懂(chang)无(sha)知(bi)的时候用某种诡异的瞎推式子方法做了一些实际上是反演的题目?!!比如这个题。。。

这道题要求满足 axbcyd gcd(x,y)=k 的数对 (x,y) 的数目,那么很容易想到可以转化成满足 akxbkckydk gcd(x,y)=1 的数对 (x,y) 的数目。再把每个询问用二维前缀和的方式拆成四个,就变成了要求解 1xn1ym 范围内的互质数对个数。接下来我们要开始画柿子啦~

要求的东西是:

i=1nj=1m[(x,y)==1]

利用公式 [n=1]=d|nμ(d) (诶话说把这公式套上来是不是就是反演的一种),我们可以把式子化成:
i=1nj=1md|(i,j)μ(d)

可以发现 d|(i,j) 的充要条件实际上就是d是i和j的公约数,那么可以继续把式子化成:
i=1nj=1md=1min(n,m)[d|i][d|j]μ(d)

把那些 移动一下排列成一个好看的样子:
d=1min(n,m)i=1n[d|i]j=1m[d|j]μ(d)

可以发现 i=1n[d|i] 就是在求 1..n 的范围内有多少d的倍数,这个东西显然就是 nd 。那么式子可以变成一个愉悦的形态:
d=1min(n,m)ndmdμ(d)

用分块枚举除法取值的方法,我们就可以在 O(n+m) 的时间复杂内解决单组询问辣!

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 60000
using namespace std;
int T,a,b,c,d,k,mu[60010],prm[60010];
bool ext[60010];
long long solve(long long n,long long m){
    long long ans,tail;
    if (n>m) swap(n,m);
    ans=0;
    for (int i=1;i<=n;i=tail+1){
        tail=min(n/(n/i),m/(m/i));
        ans+=(n/i)*(m/i)*(mu[tail]-mu[i-1]);
    }
    return ans;
}
int main()
{
    mu[1]=1;
    for (int i=2;i<=N;i++){
        if (ext[i]==false){
            prm[++prm[0]]=i;
            mu[i]=-1;
        }
        for (int j=1;j<=prm[0];j++){
            if (i*prm[j]>N) break;
            ext[i*prm[j]]=true;
            if (i%prm[j]==0){
                mu[i*prm[j]]=0;break;
            }else mu[i*prm[j]]=-mu[i];
        }
        mu[i]+=mu[i-1];
    }
    scanf("%d",&T);
    for (int wer=1;wer<=T;wer++){
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
        a--;c--;b=b/k;d=d/k;a=a/k;c=c/k;
        if (b==0||d==0) {printf("0\n");continue;}
        printf("%I64d\n",solve(b,d)-solve(a,d)-solve(c,b)+solve(a,c));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值