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