zoukankan      html  css  js  c++  java
  • HAOI2011 Problem b 洛谷P2522

    Description

    对于给出的n个询问,每次求有多少个数对(x,y),满足a≤x≤b,c≤y≤d,且gcd(x,y) = k,gcd(x,y)函数为x和y的最大公约数。


    Input

    第一行一个整数n,接下来n行每行五个整数,分别表示a、b、c、d、k。


    Output

    共n行,每行一个整数表示满足要求的数对(x,y)的个数。


    Hint

    100%的数据满足:1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000。


    Solution

    我觉得太神奇了这道题,省选题都那么恶心的吗???

    首先需要明确的是这道题不用像BZOJ2480/HDUI695那种恶心题一样要去重。(去重语句:ret+=(1-x)*x+x*(y-x)),去重的原理是,把组合按顺序、坐标写出来,对角线以上的不要就行了。

    我先就用的普通的莫比乌斯反演,f(k)表示gcd==k的(x,y)的数量,F(n)表示gcd==n的倍数,那么由反演公式F(n)=sigma n|k(f(k)) -->f(n)=sigma n|k(Mo[k/n]*F(k)),其中F(k)即k的倍数两两组合的数量,那么通过反演就计算出了f(k)的值。预处理出Mobius函数,然后就按照这个思路,需要一点容斥原理的知识,因为x的区间是[a,b],y的区间是[c,d],并不是BZOJ2480/HDUI695那个题那个样子的[1,b],[1,d](反正那个题恶心的地方在去重好吧),那么这个题的答案应该就是ans=workk(b,d)+workk(a-1,c-1)-workk(b,c-1)-workk(a-1,d),需要注意的是a和c都要-1,因为是闭区间。

    接下来就是这道题很魔鬼的地方了。

    用普通方法做只能A30%的数据,剩下的数据全都要T,因为数据组数的规模是50000,这个询问起来肯定要超时。于是yb老师和左哥大佬都给我说要分 块。

    靠,当年的蒲公英简直是心理阴影好吗??!!

    而且分块的思路也很魔鬼,处理出Mobius函数的前缀和,然后,

    然后,

    然后,

    块的大小为TMP=min(x/(x/i),y/(y/i)),然后只需要ret+=(x/i)*(y/i)*(sum[TMP]-sum[i-1])。

    你就会发现它A了(落泪了)。

    以下是AC代码:

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define maxn 50005
    using namespace std;
    int Mo[maxn],primes[maxn],num[maxn],sum[maxn];
    int cnt,T,a,b,c,d,k,ans;
    inline void Mobius(){
    	Mo[1]=1;
    	for(int i=2;i<=maxn-1;i++){
    		if(!num[i]){
    			primes[++cnt]=i;
    			Mo[i]=-1;
    		}
    		for(int j=1;j<=cnt&&i*primes[j]<maxn;j++){
    			num[i*primes[j]]=true;
    			Mo[i*primes[j]]=Mo[i]*(-1);
    			if(i%primes[j]==0){
    				Mo[i*primes[j]]=0;
    				break;
    			}
    		}
    	}
    	for(int i=1;i<=maxn-1;i++){
    		sum[i]=sum[i-1]+Mo[i];
    	}
    }
    inline int workk(int x,int y){
    	x/=k,y/=k;
    	if(x>y)swap(x,y);
    	int ret=0,TMP=0;
    	for(int i=1;i<=x;i=TMP+1){
    		TMP=min(x/(x/i),y/(y/i));
    		ret+=(x/i)*(y/i)*(sum[TMP]-sum[i-1]);
    	}
    	return ret;
    }
    int main(){
    	Mobius();
    	scanf("%d",&T);
    	for(int i=1;i<=T;i++){
    		scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
    		ans=workk(b,d)+workk(a-1,c-1)-workk(b,c-1)-workk(a-1,d);
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    以下是普通做法(30%数据):

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define maxn 50005
    using namespace std;
    int Mo[maxn],primes[maxn],num[maxn],sum[maxn];
    int cnt,T,a,b,c,d,k,ans;
    inline void Mobius(){
    	Mo[1]=1;
    	for(int i=2;i<=maxn-1;i++){
    		if(!num[i]){
    			primes[++cnt]=i;
    			Mo[i]=-1;
    		}
    		for(int j=1;j<=cnt&&i*primes[j]<maxn;j++){
    			num[i*primes[j]]=true;
    			Mo[i*primes[j]]=Mo[i]*(-1);
    			if(i%primes[j]==0){
    				Mo[i*primes[j]]=0;
    				break;
    			}
    		}
    	}
    }
    inline int workk(int x,int y){
    	x/=k,y/=k;
    	if(x>y)swap(x,y);
    	int ret=0;
    	for(int i=1;i<=x;i++){
    		ret+=(x/i)*(y/i)*Mo[i];
    	}
    	return ret;
    }
    int main(){
    	Mobius();
    	scanf("%d",&T);
    	for(int i=1;i<=T;i++){
    		scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
    		ans=workk(b,d)-workk(a-1,d)-workk(b,c-1)+workk(a-1,c-1);
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    java实现立方和等式
    java实现立方和等式
    java实现立方和等式
    java实现立方和等式
    java实现立方和等式
    java实现拉丁方块填数字
    java实现拉丁方块填数字
    java实现拉丁方块填数字
    java实现拉丁方块填数字
    IDHTTP用法详解 good
  • 原文地址:https://www.cnblogs.com/virtual-north-Illya/p/10288825.html
Copyright © 2011-2022 走看看