bzoj2301: [HAOI2011]Problem b

【题意】

  对于给出的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))

【代码】

 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 }
View Code

 

posted @ 2017-04-26 18:26  Bleacher  阅读(...)  评论(...编辑  收藏