1. 程式人生 > >題解【bzoj2301 [HAOI2011]Problem b】

題解【bzoj2301 [HAOI2011]Problem b】

Description

求有多少個數對 \((x,y)\) ,滿足$ a \leq x \leq b$ ,\(c \leq y \leq d\) ,且 \(\gcd(x,y) = k\)\(\gcd(x,y)\)函式為 \(x\)\(y\) 的最大公約數。多組詢問。\(a,b,c,d,k,T \leq 50000\)

Solution

莫比烏斯反演的經典題目QAQ

首相將問題轉化成字首上的問題。即需要求出 有多少個數對 \((x,y)\) ,滿足$ 1 \leq x \leq a$ ,\(1 \leq y \leq b\) ,且 \(\gcd(x,y) = k\)。如果能夠快速算出來這個,容斥一下就可以求出最後答案。

考慮這個怎麼求,開始推式子。這個東西顯然就是

\[\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[\gcd(i,j)=k]\]

\(k\) 提出來可得

\[\sum\limits_{i=1}^{\lfloor\frac{n}{k}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{k}\rfloor}[\gcd(i, j)=1]\]

然後把後面這個 \([\gcd(i,j)=1]\) 反演掉,得

\[\sum\limits_{i=1}^{\lfloor\frac{n}{k}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{k}\rfloor}\sum\limits_{d|\gcd(i,j)}\mu(d)\]

\(d\) 搞到前面來,得到

\[\sum\limits_{d=1}^{\lfloor \frac{n}{k} \rfloor} \mu(d)\lfloor \frac{n}{kd} \rfloor\lfloor \frac{m}{kd} \rfloor\]

好了,這個玩意可以預處理出 \(\mu\) 得字首和然後分塊完事。

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 50000; 
int k, cnt, p[N + 50], mu[N + 50], flag[N + 50], sum[N + 50]; 
inline void prework() {
  flag[1] = mu[1] = 1;
  for(int i = 2; i <= N; i++) {
    if(!flag[i]) {
      p[++cnt] = i; mu[i] = -1; 
    } for(int j = 1; j <= cnt && i * p[j] <= N; j++) {
      flag[i * p[j]] = 1;
      if(i % p[j] == 0) {
        mu[i * p[j]] = 0; break; 
      } mu[i * p[j]] = mu[i] * -1; 
    }
  } for(int i = 1; i <= N; i++) sum[i] = sum[i - 1] + mu[i]; 
}
inline ll calc(int n, int m) {
  if(n > m) swap(n, m); ll ret = 0; 
  for(int l = 1, r; l <= n / k; l = r + 1) {
    r = min(n / (n / l), m / (m / l)); 
    ret += 1ll * (n / (l * k)) * (m / (l * k)) * (sum[r] - sum[l - 1]); 
  } return ret; 
}
int main() {
  int T; prework(); 
  scanf("%d", &T); 
  while(T--) {
    int a, b, c, d; 
    scanf("%d %d %d %d %d", &a, &b, &c, &d, &k);
    printf("%lld\n", calc(a - 1, c - 1) - calc(b, c - 1) - calc(d, a - 1) + calc(b, d)); 
  }
  return 0; 
}