hzoj 2301(莫比乌斯反演)
题意
对于给出的n个询问,每次求有多少个数对(x,y),满足a≤x≤b,c≤y≤d,且gcd(x,y) = k,gcd(x,y)函数为x和y的最大公
数。
思路:
与先前的那个相比,这次a,c并不一定为一。所以先用的莫比乌斯+容斥定理但是TL
然后发现可以进一步有优化
可以发现8/3 和 8/4都等于2.所以我们可以分段计算,用sum记录mu的和,每次求出a/i的最大位置I,在i至l这段数中,a/i的值都是相同的,便可以每次循环计算出一段数的值,而且当数值越大时,重复越多。
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <queue> #include <vector> #include <algorithm> #include <functional> typedef long long ll; using namespace std; const int inf = 0x3f3f3f3f; const int maxn = 1e6+10; int is_prime[maxn]; int prime[maxn]; int sum[maxn]; int mu[maxn]; int tot; int a,b,c,d,k; ll Min(ll x,ll y) { if(x < y) return x; else return y; } void Moblus() { tot = 0; memset(is_prime,0,sizeof(is_prime)); mu[1] = 1; for(int i = 2; i <= maxn; i++) { if(!is_prime[i]) { prime[tot++] = i; mu[i] = -1; } for(int j = 0; j < tot; j++) { if(prime[j]*i>maxn) break; is_prime[i*prime[j]] = 1; if(i % prime[j]) //prime[j]不重复 { mu[i*prime[j]] = -mu[i]; } else { mu[i*prime[j]] = 0; break; } } } } ll gett(int n,int m) //分块优化 { ll ret = 0; int i,last; if(n > m) swap(n,m); for(i = 1,last = 0;i<=n;i=last+1) { last = Min(n/(n/i),m/(m/i)); //求为n/i时的最远位置 ret += (ll)(n/i)*(m/i)*(sum[last]-sum[i-1]); } return ret; } int main() { int T; Moblus(); sum[0] = 0; for(int i = 1; i <= 50000; i++) sum[i] = sum[i-1]+mu[i]; scanf("%d",&T); while(T--) { scanf("%d%d%d%d%d",&a,&b,&c,&d,&k); ll ans; ans=gett(b/k,d/k)-gett((a-1)/k,d/k)-gett((c-1)/k,b/k)+gett((a-1)/k,(c-1)/k); printf("%lld\n",ans); } return 0; }