gym 101982 B题 Coprime Integers

题目链接:https://codeforces.com/gym/101982/attachments

贴一张图吧:

gym 101982 B题 Coprime Integers_第1张图片

题目意思就是给出四个数字,a,b,c,d,分别代表两个区间[a,b],[c,d],从这两个区间里面分别拿一个数字组成(x,y),问x和y互质的组合有多少种。

这道题目好像要用莫比乌斯反演,但是目前没有了解过这个知识点,后续会补上,我用的是打表+容斥定理做的,相比于上一种方法,耗费的时间可能会多很多。我亲测是600到800ms,所以还是很有必要学莫比乌斯反演的。

接下来讲我的思路:两个区间里面所有的组合数是(b-a+1)*(d-c+1)种,我可以先算出不互质的组合的个数,再用总数减去它得到互质的组合数。

首先,假设我要算所有gcd(x,y)=2的组合数,那么在区间[a,b]里面,素因子含有2的数字个数是b/2-(a-1)/2这么多个,在区间[c,d]里面含有2这个素因子的数字的个数是d/2-(c-1)/2这么多。这两个数字相乘就是两个区间中gcd(x,y)=2的组合数字。

假如我们遍历计算1到10000000里面所有的素数(大概660000多一点),那么就会出现重复计算的情况,假如我gcd(x,y)=2和gcd(x,y)=3的情况都计算了一边,那么gcd(x,y)=6的情况就计算了两遍,那么我们就要再减去gcd(x,y)=6的情况的组合数。

这就要用到容斥定理(奇加偶减),假如一个数字n,它不同的素因子有奇数个,那么就加,如果是偶数个就减,并且它某一个素因子个数不能大于1个(6=2*3,它的素因子有2和3,素因子2有且只有一个,素因子3有且只有一个,那么这个数字我们是要计算的,另一个数字12=2*2*3,它的素因子2有2个,那么我们就不用计算它,因为它已经包含在(gcd(x,y)=2)的数量+(gcd(x,y)=3)的数量-(gcd(x,y)=6)里面了)。

那么我们现在就要先打表把所有类似于6(2*3),10(2*5),30(2*3*5),这种相同素因子只有一个的数筛出来(大概6000000个,所以花费时间有点多),然后遍历计算就可以了。

这个打表的过程可以在我们线性筛素数的过程中做到,所以这个打表是线性的。

这里面num[i]代表数字i有多少个不同的素数,例如num[30]=3,(30=2*3*5)。

flag[i]表示数字i是不是所有素数有且只有一个,如果flag[i]=true,那么这个i就是我们要找的数字。数组ok就是把这些数字存起来,等下遍历数组ok就可以了。

打表代码:

void init(){
    memset(vis,0,sizeof(vis));
    memset(flag,false,sizeof(flag));
    cnt=0;//记录素数个数 
    cc=0;//计录我们要找的数组个数 
    for(int i=2;i){
        if(vis[i]==0){
            prime[cnt++]=i;//是一个素数 
            num[i]=1;       //不同的素因子是有它自己一个,复制为1 
            ok[cc++]=i;       //保存在ok数组中 
            flag[i]=true;  //标记这个数字是我们要找的 
        }
        for(int j=0;j){
            vis[prime[j]*i]=true;
            if((i%prime[j])!=0)//在这之前我们已经知道了num[i],只要i不被prime[j]整除,那么prime[j]*i这个数字不同素因子个数就是num[i]+1 
            num[prime[j]*i]=num[i]+1;
            if(flag[i]==1&&(i%prime[j])){
     //假如flag[i]=true,说明i是我们要找的数字,并且i%prime[j]非0,那么prime[j]*i也是我们要找的数字 
                ok[cc++]=i*prime[j];
                flag[i*prime[j]]=true;
            }
            if(i%prime[j]==0)
            break;
        }
    }
}

 

完整代码:

 

#include
#include
#include
#include
#include
#include
#include
#include
#include<set>
#include
#include<string>
#include 
using namespace std;
typedef long long LL;
#define eps 1e-8
#define INF 0x3f3f3f3f
#define maxn 10000005
int prime[maxn],vis[maxn],num[maxn],ok[maxn],flag[maxn];
int n,m,k,t,cnt,cc;
void init(){
    memset(vis,0,sizeof(vis));
    memset(flag,false,sizeof(flag));
    cnt=0;//记录素数个数 
    cc=0;//计录我们要找的数组个数 
    for(int i=2;i){
        if(vis[i]==0){
            prime[cnt++]=i;//是一个素数 
            num[i]=1;       //不同的素因子是有它自己一个,复制为1 
            ok[cc++]=i;       //保存在ok数组中 
            flag[i]=true;  //标记这个数字是我们要找的 
        }
        for(int j=0;j){
            vis[prime[j]*i]=true;
            if((i%prime[j])!=0)//在这之前我们已经知道了num[i],只要i不被prime[j]整除,那么prime[j]*i这个数字不同素因子个数就是num[i]+1 
            num[prime[j]*i]=num[i]+1;
            if(flag[i]==1&&(i%prime[j])){
     //假如flag[i]=true,说明i是我们要找的数字,并且i%prime[j]非0,那么prime[j]*i也是我们要找的数字 
                ok[cc++]=i*prime[j];
                flag[i*prime[j]]=true;
            }
            if(i%prime[j]==0)
            break;
        }
    }
}
int main()
{
    int a,b,c,d;
    init();
    sort(ok,ok+cc);
    while(scanf("%d%d%d%d",&a,&b,&c,&d)!=EOF){
        LL ans=0;
        int maxx=min(b,d);//记录一下两个,区间最小的右边界,可有可无吧 ,好像影响不大 
        for(int i=0;i){
            int now=ok[i];
            if(num[now]%2){
     //计数加 ,注意答案非常大,要用long long 
                ans+=(LL)(b/now-(a-1)/now)*(d/now-(c-1)/now);
            }else{
     //偶数减 
                ans-=(LL)(b/now-(a-1)/now)*(d/now-(c-1)/now);
            }
        }
        printf("%lld\n",(LL)(b-a+1)*(d-c+1)-ans);
    }
    return 0;
}

待补充。。。。。。

来补充了,额,请看下面大佬介绍莫比乌斯反演,完......

补充:https://www.cnblogs.com/chenyang920/p/4811995.html

转载于:https://www.cnblogs.com/6262369sss/p/10785247.html

你可能感兴趣的:(gym 101982 B题 Coprime Integers)