[莫比乌斯反演+容斥+分块求和] 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;
}
发布了330 篇原创文章 · 获赞 35 · 访问量 11万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览