hdu 1695 GCD (欧拉函数+容斥原理+素因子分解) :http://acm.hdu.edu.cn/showproblem.php?pid=1695
题面描述:
Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 8787 Accepted Submission(s): 3261
2 1 3 1 5 1 1 11014 1 14409 9
Case 1: 9 Case 2: 736427HintFor the first sample input, all the 9 pairs of numbers are (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 5), (3, 4), (3, 5).
题目大意:
在x区间[a,b]和y区间[c,d]中出满足gcd(x,y)=k的(x,y)的对数。
题目分析:
数据不是暴力就能过的,需要用到莫比乌斯反演的思想,具体分析如下:
(1)由于gcd(x,y)=k满足gcd(x/k,y/k)=1,所以区间可以缩小为:[1/k.b/k],[1/k,d/k],问题也转化成为取两区间中的元素x,y,使得gcd(x,y)=1;
(2)由于gcd(y,x)=1和gcd(x,y)=1是相同的,假设x<y,于是可以先取小区间进行讨论,由于gcd(x,y)=1,利用欧拉函数,所以小区间可以计算为:
ans+=phi[1]+phi[2]+...+phi[b](用欧拉函数的递推形式保存起来即可);
(3)对于[b/k+1,d/k],设y为区间的一个元素,则可以对y进行素因子分解,于是得到集合:[p1,p2,p3,...],其中pi为素数,虽然是要求gcd(x,y)=1的组合,但反过来也可以求gcd(x,y)!=1的组合,于是可以用容斥原理进行统计能被这些素数整除的数的个数,最后相减求补数即可加到ans.
代码实现:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #define Maxn 100001 using namespace std; int noprime[10000],no; int t,a,b,c,d,k,sum,casenum; void dfs(int i,int nu,int x,int mu) //容斥原理一般和递归一起用 { if(nu==x) { sum+=b/mu; return; } if(i==no) return; dfs(i+1,nu+1,x,mu*noprime[i]); dfs(i+1,nu,x,mu); return; } int rong() //容斥定理 { int s=0; for(int i=1; i<=no; i++) { sum=0; dfs(0,0,i,1); if(i&1) s+=sum;//奇数加,偶数减 else s-=sum; } return b-s;//求的是互质的,取反 } int main() { int phi[Maxn],prime[10000];//欧拉函数的递推形式 bool boo[Maxn]; for(int i=1; i<Maxn; i++) phi[i]=i; for(int i=2; i<Maxn; i+=2) phi[i]/=2; for(int i=3; i<Maxn; i+=2) { if(phi[i]==i) { for(int j=i; j<Maxn; j+=i) { phi[j]=phi[j]/i*(i-1); } } } memset(boo,0,sizeof(boo));//线性筛素数 boo[0]=boo[1]=1; int p=0; for(int i=2; i<Maxn; i++) { if(!boo[i]) prime[p++]=i; for(int j=0; j<p&&i*prime[j]<Maxn; j++) { boo[i*prime[j]]=1; if(!(i%prime[j])) break; } } casenum=0; scanf("%d",&t); while(t--) { scanf("%d%d%d%d%d",&a,&b,&c,&d,&k); if(k==0) //注意k等于0的情况 { printf("Case %d: 0\n",++casenum); continue; } b/=k; d/=k; if(b>d) { int w=b; b=d; d=w; } long long ans=0; for(int i=1; i<=b; i++) { ans+=phi[i]; } for(int i=b+1; i<=d; i++) { no=0; int aa=i; for(int j=0; j<p&&prime[j]*prime[j]<=aa; j++) if(!(aa%prime[j])) { noprime[no++]=prime[j]; aa/=prime[j]; while(aa%prime[j]==0) { aa/=prime[j]; } // do // aa/=prime[j]; // while(aa%prime[j]==0); } if(aa>1) { noprime[no++]=aa; } ans+=rong(); } printf("Case %d: %lld\n",++casenum,ans); } return 0; }