题目链接:https://codeforces.com/gym/101982/attachments
贴一张图吧:
题目意思就是给出四个数字,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<maxn;i++){ if(vis[i]==0){ prime[cnt++]=i;//是一个素数 num[i]=1; //不同的素因子是有它自己一个,复制为1 ok[cc++]=i; //保存在ok数组中 flag[i]=true; //标记这个数字是我们要找的 } for(int j=0;j<cnt&&(i*prime[j]<maxn);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<iostream> #include<cstring> #include<algorithm> #include<queue> #include<map> #include<stack> #include<cmath> #include<vector> #include<set> #include<cstdio> #include<string> #include<deque> 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<maxn;i++){ if(vis[i]==0){ prime[cnt++]=i;//是一个素数 num[i]=1; //不同的素因子是有它自己一个,复制为1 ok[cc++]=i; //保存在ok数组中 flag[i]=true; //标记这个数字是我们要找的 } for(int j=0;j<cnt&&(i*prime[j]<maxn);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<cc&&ok[i]<=maxx;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 ",(LL)(b-a+1)*(d-c+1)-ans); } return 0; }
待补充。。。。。。
来补充了,额,请看下面大佬介绍莫比乌斯反演,完......