[莫比乌斯反演+容斥+分块求和] BZOJ2301: [HAOI2011]Problem b

题意

对于给出的T个询问,每次求有多少个数对(x,y),满足a≤x≤b,c≤y≤d,且gcd(x,y) = k。
1≤T,a,b,c,d,k≤50000

题解

莫比乌斯反演经典入门题。

首先用容斥,把问题转化为1<=x<=n且1<=y<=m的。
f(i) 表示满足gcd(x,y)等于i的有序数对(x,y)的个数。(1<=x<=n且1<=y<=m)
构造 F(i)=i|df(d) ,即满足i|gcd(x,y)的有序数对(x,y)的个数。
F(i) 很好求,只有x和y都是i的倍数即可,所以 F(i)=nimi
下面进行反演:

F(i)=i|df(d)f(i)=i|dμ(di)F(d)=i|dμ(di)ndmd

还可以转化一下:使gcd(x,y)=K的方案数(1<=x<=n且1<=y<=m) 与 使gcd(x,y)=1的方案数(1<=x<=n/k且1<=y<=m/k) 是等价的。
然后我们的目标就是求得 f(1)=min(n/K,m/K)dμ(d)n/Kdm/Kd
这个如果O(n)求的话还是太慢,要想更优的办法。
注意到 n/Kd 这种东西的取值是一段一段的,然后后我们对 μ 求前缀和,然后分块求和,把一段连续的 n/Kdm/Kd 值相同的一起算。
具体实现:

    if(n>m) swap(n,m);  
    int res=0;  
    for(int d=1,nxt=0;d<=n;d=nxt+1){  
        nxt=min(n/(n/d),m/(m/d));  
        res+=(n/d)*(m/d)*(sum_mu[nxt]-sum_mu[d-1]);  
    } 

这样就变成 O(n) 的了。

总复杂度 O(Tn)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1000005, N=1000000;
int _test,K,p[maxn],mu[maxn],sum_mu[maxn];
bool vis[maxn];
void get_mu(){
    memset(vis,1,sizeof(vis));
    mu[1]=1;
    for(int i=2;i<=N;i++){
        if(vis[i]) p[++p[0]]=i, mu[i]=-1;
        for(int j=1;j<=p[0]&&i*p[j]<=N;j++){
            vis[i*p[j]]=false;
            if(i%p[j]==0){ mu[i*p[j]]=0; break; }
            mu[i*p[j]]=-mu[i];
        }
    }
    for(int i=1;i<=N;i++) sum_mu[i]=sum_mu[i-1]+mu[i];
}
int get(int n,int m){
    n/=K; m/=K;
    if(n>m) swap(n,m);  
    int res=0;  
    for(int d=1,nxt=0;d<=n;d=nxt+1){  
        nxt=min(n/(n/d),m/(m/d));  
        res+=(n/d)*(m/d)*(sum_mu[nxt]-sum_mu[d-1]);  
    }  
    return res;  
}  
int main(){
    freopen("bzoj2301.in","r",stdin);
    freopen("bzoj2301.out","w",stdout);
    get_mu();
    scanf("%d",&_test);
    while(_test--){
        int a,b,c,d;
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&K);
        printf("%d\n",get(b,d)-get(a-1,d)-get(b,c-1)+get(a-1,c-1));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值