P2522 [HAOI2011]Problem b
P2522 [HAOI2011]Problem b
https://www.luogu.com.cn/problem/P2522
题目描述
给出 \(a,b,c,d,k\),求满足 \(a \leq x \leq b,\ c \leq y \leq d,\ gcd(i,j) = k\)的 \((x,y)\)有多少对。
解题思路
已知答案为 \(\sum_{i = a}^b\sum_{j = c}^d[gcd(i,j) == k]\)
因为 \(x,y\)的上下界不确定,因此不好求解,但是对于 \(\sum_{i = 1}^n\sum_{j = 1}^m[gcd(i,j) == k]\),我们可能比较容易求得。
相当于将答案分割成了四部分 \(ans_1 = \sum_{i = 1}^b\sum_{j = 1}^d\)、\(ans_2 = \sum_{i = 1}^{a-1}\sum_{j = 1}^d\)、\(ans_3 = \sum_{i = 1}^b\sum_{j = 1}^{c-1}\)、\(ans_4 = \sum_{i = 1}^{a-1}\sum_{j=1}^{c-1}\),
因此答案(容斥原理)\(ans = ans1 - ans2 - ans3 + ans4\)。
下面讨论如何求解 \(\sum_{i = 1}^n\sum_{j = 1}^m[gcd(i,j) == k]\):
若 \(gcd(x,y) = k\),则设 \(x = ki,\ y = kj,\ 且gcd(i,j) = 1\),因此我们可以枚举 此时的 \(i,j\):
\([gcd(i,j) == 1] \Rightarrow \epsilon(gcd(i,j))\),所以原式等价于:\(\sum_{i = 1}^{\lfloor \frac nk \rfloor}\sum_{j = 1}^{\lfloor \frac mk \rfloor}\epsilon(gcd(i,j))\)
因为 \(\mu * 1 = \epsilon\),所以 \(\epsilon(gcd(i,j)) = \sum_{d|gcd(i,j)} \mu(d)\)
即: \(\sum_{i = 1}^{\lfloor \frac nk \rfloor}\sum_{j = 1}^{\lfloor \frac mk \rfloor}\epsilon(gcd(i,j)) = \sum_{i = 1}^{\lfloor \frac nk \rfloor}\sum_{j = 1}^{\lfloor \frac mk \rfloor}\sum_{d|gcd(i,j)}\mu(d)\)
交换求和顺序(枚举\(d\),计算 \((i,j)\)为 \(d\)的倍数的对数):
因为 \(\sum_{i = 1}^n[d|i] = \frac nd\),所以原式等价于: \(\sum_{d = 1}^{min(n/k,m/k)}\mu(d)\frac n{kd} \frac m{kd}\)
因为此题的 \(T\)很大,所以暴力枚举 \(d\)会\(TLE\),因此需要用到数论分块,同时预处理出\(\mu(x)\)函数的前缀和计算即可。
Code
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+7;
int a,b,c,d,k;
int prime[MAXN],prime_tot;
bool prime_tag[MAXN];
int mu[MAXN];
void init(){
mu[1] = 1;
for(int i = 2; i <= MAXN; i++){
if(!prime_tag[i]){
prime[++prime_tot] = i;
mu[i] = -1;
}
for(int j = 1; j <= prime_tot; j++){
if(i * prime[j] > MAXN)break;
prime_tag[i * prime[j]] = true;
if(i % prime[j] == 0){
mu[i * prime[j]] = 0;
break;
}else{
mu[i * prime[j]] = -mu[i];
}
}
}
for(int i = 1; i < MAXN; i++)mu[i] += mu[i-1];
}
int slove(int n,int m){
int up = min(n / k, m / k);
int ret = 0;
for(int l = 1,r; l <= up; l = r + 1){
r = min(n / (n / l),m / (m / l));
ret += (mu[r] - mu[l-1]) * (n / k / l) * (m / k / l);
}
return ret;
}
void solve(){
cin >> a >> b >> c >> d >> k;
cout << slove(b,d) - slove(a-1,d) - slove(b,c-1) + slove(a-1,c-1) << "\n";
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
init();
int t;
cin >> t;
while(t--){
solve();
}
return 0;
}