BZOJ 2301 HAOI2011 Problem b 莫比乌斯反演
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2301
题意概述:
对于给出的n个询问,每次求有多少个数对(x,y),满足a≤x≤b,c≤y≤d,且gcd(x,y) = k。
1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000.
分析:
这实际上就是莫比乌斯反演的裸题!
但是我为什么要专门发出来呢?
因为我想总结一下代码的问题和一般思路......
首先观察题目询问,可以发现询问可以用矩形前缀和来回答。于是问题简化为给出a,b,k,求gcd(x,y)=k的数对数量,1<=x<=a,1<=y<=b。
我们令函数,这就是我们的计算目标,但是显然这个东西求起来非常的麻烦啊!!评测不能承受之慢啊!!!
于是我们考虑莫比乌斯反演,构造一个更加简单易求的函数F(k),F(k)可以由f(d)(F有两种考虑方向,k|d或者d|k)得到。
考虑两种构造的方式,最后构造出来的F(k)意义为一定范围内k|gcd(x,y)的(x,y)的对数,。
其中F(k)是非常好求的,。
根据莫比乌斯反演,我们得到:,即。
可以发现其中d/k的值一定是连续的,不妨令n<=m,那么我们可以把这个柿子变成:。
同时我们发现n最多有2*sqrt(n)种被整除的结果,于是我们只需要维护一个μ的前缀和就可以大幅提高询问的效率,利用经典方法在O(sqrt(N))的时间内回答单组询问(具体讲解见代码注释)。
时间复杂度O(N*sqrt(N))。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstdlib> 5 #include<algorithm> 6 #include<cmath> 7 #include<queue> 8 #include<set> 9 #include<map> 10 #include<vector> 11 #include<cctype> 12 using namespace std; 13 const int maxn=50005; 14 const int maxp=5200; 15 typedef long long LL; 16 17 int N,a,b,c,d,k; 18 int pri[maxp],tot,mu[maxn],sum[maxn]; bool ntp[maxn]; 19 20 void get_mu() 21 { 22 ntp[0]=ntp[1]=1,mu[1]=1; 23 for(int i=2;i<=50000;i++){ 24 if(!ntp[i]) pri[++tot]=i,mu[i]=-1; 25 for(int j=1;j<=tot&&1ll*pri[j]*i<=50000;j++){ 26 ntp[pri[j]*i]=1; 27 if(i%pri[j]==0){ mu[i*pri[j]]=0; break; } 28 mu[i*pri[j]]=-mu[i]; 29 } 30 } 31 for(int i=1;i<=50000;i++) sum[i]=sum[i-1]+mu[i]; 32 } 33 LL calc(int n,int m) 34 { 35 if(n>m) swap(n,m); 36 n/=k,m/=k; 37 LL re=0; 38 for(int i=1,last=0;i<=n;i=last+1){ 39 last=min(n/(n/i),m/(m/i));//令x=n/i,那么可以发现n/x=max{i|n/i=x},只要n/i和m/i中间有一个变化了F(d)就会改变,所以取min。 40 re+=1ll*(sum[last]-sum[i-1])*(n/i)*(m/i);//因为n最多被乘除的结果最多有2*sqrt(N)种,所以单次询问复杂度O(sqrt(N))。 41 } 42 return re; 43 } 44 int main() 45 { 46 freopen("test.in","r",stdin); 47 freopen("test.out","w",stdout); 48 get_mu(); 49 scanf("%d",&N); 50 for(int i=1;i<=N;i++){ 51 scanf("%d%d%d%d%d",&a,&b,&c,&d,&k); 52 printf("%lld\n",calc(b,d)-calc(a-1,d)-calc(b,c-1)+calc(a-1,c-1)); 53 } 54 return 0; 55 }