【题意】
对于给出的n个询问,每次求有多少个数对(x,y),满足a≤x≤b,c≤y≤d,且gcd(x,y) = k,gcd(x,y)函数为x和y的最大公约数。
【题解】
这算是一道莫比乌斯反演的入门题吧,花了好多时间才略有体会。。。
首先对于一个询问,直接在原有范围内求出个数显然有点困难,利用容斥简化。
设ANS(N,M)为1<=X<=N,1<=Y<=M时GCD(X,Y)=K的对数,
最后的答案就是ANS(B,D)-ANS(A-1,D)-ANS(B,C-1)+ANS(A-1,C-1)。
现在问题化为求ANS(N,M)。
设f(d)为N,M范围内最大公约数刚好为d的数对个数,F(d)为含公约数d的数对个数。
F(d)=$\sum_{d|i}^{}f(i) $(i<=n)
我们要求的就是f(d)
反演一下得到
f(d)=$\sum_{d|i}^{}F(i)\cdot μ(i/d) (i<=n)$
f(k)就是我们希望的值。
暴力求显然会超时啊!!!!!(一开始太天真没仔细算复杂度T掉了)
然后有个神奇的性质:$\left \lfloor \frac{n}{i} \right \rfloor$的不同值个数不超过$2\sqrt{n}$个,
于是$\left \lfloor \frac{n}{i} \right \rfloor \left \lfloor \frac{m}{i} \right \rfloor$的不同值个数也不超过$2\sqrt{n}$+$2\sqrt{m}$,(这个可以自己YY一下)
而且相同的值是连续的一段i所产生的。
所以我们先预处理出前缀和,求出每一段i的μ值,一段一段的加,次数不超过sqrt(n)次。
时间复杂度O(T*sqrt(n))
【代码】
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <iostream> 2 #include <cstdio> 3 #define ll long long 4 #define N 50010 5 using namespace std; 6 int Q,a,b,c,d,k,mu[N],prime[N],sum[N],cnt; 7 bool flag[N]; 8 int read() 9 { 10 int x=0;char ch;int sign=1; 11 do{ch=getchar();if(ch=='-')sign*=-1;}while (ch<'0' || ch>'9'); 12 do{x=x*10+ch-48;ch=getchar();}while (ch>='0' && ch<='9'); 13 return x*=sign; 14 } 15 void Pre() 16 { 17 mu[1]=1; 18 for (int i=2;i<N;++i) 19 { 20 if (!flag[i]) prime[++cnt]=i,mu[i]=-1; 21 for (int j=1;j<=cnt;++j) 22 { 23 if (i*prime[j]>=N) break; 24 flag[i*prime[j]]=1; 25 if (i%prime[j]==0) break; 26 mu[i*prime[j]]=-mu[i]; 27 } 28 } 29 for (int i=1;i<N;++i) sum[i]=sum[i-1]+mu[i]; 30 } 31 ll solve(int n,int m) 32 { 33 ll ans=0; 34 n/=k;m/=k; 35 int l=n<m?n:m,last; 36 for (int i=1;i<=l;i=last+1) 37 { 38 last=min(n/(n/i),m/(m/i)); 39 ans+=(ll)(n/i)*(m/i)*(sum[last]-sum[i-1]); 40 } 41 return ans; 42 } 43 int main() 44 { 45 Pre(); 46 Q=read(); 47 while (Q--) 48 { 49 a=read()-1;b=read();c=read()-1;d=read();k=read(); 50 printf("%lld\n",solve(b,d)-solve(a,d)-solve(b,c)+solve(a,c)); 51 } 52 return 0; 53 }