原题链接差不多算自己推出来的第一道题QwQ
题目大意
\(T\)组询问,每次问你\(1\leqslant x\leqslant N\),\(1\leqslant y\leqslant M\)中有多少\((x,y)\)满足\(gcd(x,y)\in \mathbb{P}\)
数据范围
\(T=10000\),\(1\leqslant N,M\leqslant 10000000\)
显然,暴力不可做。
这种公约数计数的题貌似大多都是用莫比乌斯反演做的?套路啊,套路。
首先,我们先很套路地设一个函数\(f(n)\)(方括号的意思是,若里面的表达式为真,则值为\(1\),否则为\(0\)),
然后,我们再很套路的设一个函数 \(F(n)\),并定义
由乘法原理,易得 \(F(n)=\left \lfloor \frac{N}{n} \right \rfloor \left \lfloor \frac{M}{n} \right \rfloor\)。然后,由 \(f(n)\)和 \(F(n)\)的定义,显然有 \(F(n)=\sum\limits_{n|d}f(d)\)
反演一波,得到
接下来就是简(ma)单(fan)的化简环节了,令答案为 \(A\),可得
令 \(t=\frac{d}{p}\),代入并继续化简
令 \(T=pt\),代入
交换和号,推出
将 \(\left \lfloor \frac{N}{T} \right \rfloor \left \lfloor \frac{M}{T} \right \rfloor\)提到前面,化简到最终式子
终于写完了,巨长的 \(L^AT_EX\)
观察式子,后面的一部分可以用前缀和搞定,前面的就是整除分块的拿手好戏了。注意,因为 \(N,M\)的值可能不同,所以每次更新 \(r\)的值时,要取 \(r=min\{\frac{N}{\left \lfloor \frac{N}{l} \right \rfloor},\frac{M}{\left \lfloor \frac{M}{l} \right \rfloor}\}\)
其他的看代码吧:
#include
using namespace std;
#define N 10000000
int T, n, m, cnt, ans, mu[N+5], sum[N+5], vis[N+5], prime[N+5];
void get_mu(int lim) {
mu[1] = 1, vis[1] = 1;
for(int i = 2; i <= lim; ++i) { //筛素数时计算μ函数
if(!vis[i]) prime[++cnt] = i, mu[i] = -1;
for(int j = 1; j <= cnt && i*prime[j] <= lim; ++j) {
vis[i*prime[j]] = 1;
if(i%prime[j] == 0) break;
else mu[i*prime[j]] = -mu[i];
}
}
for(int j = 1; j <= cnt; ++j)
for(int i = 1; i*prime[j] <= lim; ++i)
sum[i*prime[j]] += mu[i];
for(int i = 1; i <= lim; ++i) sum[i] += sum[i-1];//计算前缀和
}
void init() {
cin >> T;
get_mu(N);
}
int main() {
init();
while(T--) {
cin >> n >> m;
long long ans = 0; //要开long long
int t = min(n, m);
for(int l = 1, r; l <= t; l = r+1) {
r = min(n/(n/l), m/(m/l));
ans += 1LL*(n/l)*(m/l)*(sum[r]-sum[l-1]); //就是最后推出的那个式子
}
cout << ans << endl;
}
return 0;
}