洛谷 P2522 [HAOI2011]Problem b
题意
有(n)组询问,每次给出(a,b,c,d,k),求(sumlimits_{x=a}^{b}sumlimits_{y=c}^{d}[gcd(x,y)=k])
思路
容斥原理+数论分块+莫比乌斯反演
我做的莫比乌斯反演的第一道题= =
设(f(n,m)=sumlimits_{i=1}^{n}sumlimits_{j= 1}^{m}[gcd(i,j)=k])
那么根据容斥原理,题目中的式子就转化成了(f(b,d)-f(b, c - 1) - f(a - 1,d) + f(a - 1, c - 1))
所以我们接下来的问题就转化为了如何求(f)的值
现在来化简(f)的值
- 容易得出原式等价于
[sumlimits_{i = 1}^{lfloorfrac{n}{k}
floor}sumlimits_{j = 1}^{lfloorfrac{m}{k}
floor}[gcd(i,j) = 1]
]
- 因为(epsilon(n) =sumlimits_{d|n}mu(d)=[n=1]),由此可将原式化为
[sumlimits_{i=1}^{lfloorfrac{n}{k}
floor}sumlimits_{j=1}^{lfloorfrac{m}{k}
floor}sumlimits_{d|gcd(i,j)}mu(d)
]
- 改变枚举对象并改变枚举顺序,先枚举(d),得
[sumlimits_{d=1}^{min(n,m)}mu(d)sumlimits_{i=1}^{lfloorfrac{n}{k}
floor}[d|i]sumlimits_{j=1}^{lfloorfrac{m}{k}
floor}[d|j]
]
也就是说当(d|i)且(d|j)时,(d|gcd(i,j))
- 易得(1sim lfloorfrac{n}{k} floor)中一共有(lfloorfrac{n}{dk} floor)个(d)的倍数,同理(1sim lfloorfrac{m}{k} floor)中一共有(lfloorfrac{m}{dk} floor)个(d)的倍数,于是原式化为
[sumlimits_{d=1}mu(d)lfloorfrac{n}{dk}
floorlfloorfrac{m}{dk}
floor
]
此时已经可以(O(n))求解,但是过不了,因为有很多值相同的区间,所以可以用数论分块来做
先预处理(mu),再用数论分块,复杂度(O(n+Tsqrt n))
我的代码每次得分玄学,看评测机心情,建议自己写
代码
/*
Author:loceaner
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int A = 1e6 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
inline int read() {
char c = getchar(); int x = 0, f = 1;
for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
return x * f;
}
int n, a, b, c, d, k, cnt, p[A], mu[A], sum[A];
bool vis[A];
void getmu() {
int MAX = 50010;
mu[1] = 1;
for (int i = 2; i <= MAX; i++) {
if (!vis[i]) mu[i] = -1, p[++cnt] = i;
for (int j = 1; j <= cnt && i * p[j] <= MAX; j++) {
vis[i * p[j]] = true;
if (i % p[j] == 0) break;
mu[i * p[j]] -= mu[i];
}
}
for (int i = 1; i <= MAX; i++) sum[i] = sum[i - 1] + mu[i];
}
int work(int x, int y) {
int ans = 0ll;
int max = min(x, y);
for (int l = 1, r; l <= max; l = r + 1) {
r = min(x / (x / l), y / (y / l));
ans += (1ll * x / (l * k)) * (1ll * y / (l * k)) * 1ll * (sum[r] - sum[l - 1]);
}
return ans;
}
void solve() {
a = read(), b = read(), c = read(), d = read(), k = read();
cout << work(b, d) - work(a - 1, d) - work(b, c - 1) + work(a - 1, c - 1) << '
';
}
signed main() {
getmu();
int T = read();
while (T--) solve();
return 0;
}