题解 P5435 【基于值域预处理的快速 GCD】
moongazer
2020-01-24 00:35:24
# 一种$O($值域$)$时间预处理$O(1)$时间求最大公约数($\gcd$)的算法
[$\mathfrak{View\space it\space on\space my\space Blog}$](https://blog.seniorious.cc/2020/quick-gcd/)
Update in 2020.08.07:修正了一处错误,感谢@Kinesis的指正
## 一些约定
1. $N$为询问的值域
2. $Prime$为全体素数集合
3. 集合$\{a_1,a_2,\cdots,a_m\}$是$n$的分解,当且仅当$a_1\times a_2\times\cdots\times a_m=n$
## 原理
### 定理一
#### 内容
可以将值域中的每个$x$分解成$\{a,b,c\}$,满足$a,b,c\leq\sqrt{x}$或$\in Prime$(定义这种分解为合法分解)
#### 证明
不妨设$a\leq b\leq c$若$c\notin Prime$且$c>\sqrt{x}$,则$c$可分解为$\{d,e\}$且$d\leq e$有$d\leq\sqrt{x}$,而$a\times b=\frac{x}{c}<\sqrt{x}$则有$n$的分解$\{d,a\times b,e\}$,若$e>\sqrt{x}$则可按该规律一直分解直到$e\in Prime$或$\leq\sqrt{x}$
### 定理二
#### 内容
对于询问$\gcd(x,y)$,分别考虑$a,b,c$对答案的贡献,$a$对答案的贡献为$\gcd(a,y)$,再将$y$除以$\gcd(a,y)$(这个因子已经被算过,不能重复计算),再对$b,c$干同样的事,三个贡献相乘即为$\gcd(x,y)$
#### 证明
> 易得引理若$r\mid p, r\mid q$则$\gcd(p,q)=r\times\gcd(\frac{p}{r},\frac{q}{r})$
分别代入$\left\{
\begin{aligned}
&p_1=a\times b\times c,q_1=y,r_1=\gcd(a,q_1) \\
&p_2=b\times c,q_2=\frac{q_1}{r_1},r_2=\gcd(b,q_2) \\
&p_3=c,q_3=\frac{q_2}{r_2},r_3=\gcd(c,q_3)
\end{aligned}
\right.
$即可
## 实现
我们发现实现的难点在于如何在$O(N)$时间内进行分解,询问部分较为容易
### 分解
对于$x\geq2$,找到$x$的最小质因子$p$以及$\frac{x}{p}$的合法分解$\{a_0,b_0,c_0\}$且$a_0\leq b_0\leq c_0$,$x$的一种合法分解即为$\{a_0\times p,b_0,c_0\}$的升序排序
考虑证明:
1. $x\in Prime$时显然成立,分解为$\{1,1,x\}$
2. 当$p\le\sqrt[4]{x}$时将$a_0\leq\sqrt[3]{\frac{x}{p}}$带入有$a_0\times p\le\sqrt{x}$
3. 考虑$p>\sqrt[4]{x}$的情况,$\left(1.\right)$ $a_0=1$,显然有$a_0\times p=p\le\sqrt{x}$;$\left(2.\right)$ $a\neq1$,由于$x$不是素数,$\frac{x}{p}$的最小质因子$q$即为$x$的第二小质因子,一定$\geq p$,而$a_0,b_0,c_0$都为$\frac{x}{p}$的非$1$因子,有$p\leq q\leq a_0\leq b_0\leq c_0$,$p\times a_0\times b_0\times c_0>(\sqrt[4]{x})^4=x$与$p\times a_0\times b_0\times c_0=x$相矛盾,故不存在此情况
所以只用跑一次线性筛,用最小质因子更新即可,然后预处理出$\sqrt{n}\times\sqrt{n}$的$\gcd$数组
代码如下
```cpp
// fac为合法分解,isp表示是否非质数,pri为质数数组,tot为pri的大小,pre为预处理的gcd数组,M为值域,T为sqrt(M)
void work() {
fac[1][0] = fac[1][1] = fac[1][2] = 1;
for (int i = 2; i <= M; ++i) {
if (!isp[i]) {
fac[i][0] = fac[i][1] = 1;
fac[i][2] = i;
pri[++tot] = i;
}
for (int j = 1; pri[j] * i <= M; ++j) {
int tmp = pri[j] * i;
isp[tmp] = true;
fac[tmp][0] = fac[i][0] * pri[j];
fac[tmp][1] = fac[i][1];
fac[tmp][2] = fac[i][2];
if (fac[tmp][0] > fac[tmp][1]) {
fac[tmp][0] ^= fac[tmp][1] ^= fac[tmp][0] ^= fac[tmp][1];
}
if (fac[tmp][1] > fac[tmp][2]) {
fac[tmp][1] ^= fac[tmp][2] ^= fac[tmp][1] ^= fac[tmp][2];
}
// 对于整数运算a ^= b ^= a ^= b等价于swap(a, b)这里就是手动进行length = 3的排序
if (i % pri[j] == 0) {
break;
}
}
}
for (int i = 0; i <= T; ++i) {
pre[0][i] = pre[i][0] = i;
}
for (int i = 1; i <= T; ++i) {
for (int j = 1; j <= i; ++j) {
pre[i][j] = pre[j][i] = pre[j][i % j];
}
}
}
```
### 查询
若当前枚举的为$a$,只需注意$a>\sqrt{N}$时分$a\mid y$和$a\nmid y$两种情况即可
以下为代码
```cpp
int gcd(int a, int b) {
// 不想写if-else所以用三目运算符代替并缩进了一下
int ans = 1;
for (int i = 0; i < 3; ++i) {
int tmp = (fac[a][i] > T) ?
(b % fac[a][i] ?
1
: fac[a][i]
)
: pre[fac[a][i]][b % fac[a][i]];
b /= tmp;
ans *= tmp;
}
return ans;
}
```
## 本题做法
基本上已经没了,就是求$\gcd$然后按题目给的算,放个代码吧
```cpp
const int N = 5000;
const int M = 1000000;
const int T = 1000;
const int Mod = 998244353;
void work();
int gcd(int, int);
int pre[T + 2][T + 2];
int a[N + 2], b[N + 2];
int fac[M + 2][3];
bool isp[M + 2];
int pri[M / 10], tot;
int n;
int main () {
work();
read(n);
for (int i = 1; i <= n; ++i) {
read(a[i]);
}
for (int i = 1; i <= n; ++i) {
read(b[i]);
}
for (int i = 1; i <= n; ++i) {
int ans = 0;
for (int j = 1, now = i; j <= n; ++j, now = now * 1ll * i % Mod) {
ans = (ans + int(now * 1ll * gcd(a[i], b[j]) % Mod)) % Mod;
}
write(ans), EL;
}
return 0;
}
void work() {
fac[1][0] = fac[1][1] = fac[1][2] = 1;
for (int i = 2; i <= M; ++i) {
if (!isp[i]) {
fac[i][0] = fac[i][1] = 1;
fac[i][2] = i;
pri[++tot] = i;
}
for (int j = 1; pri[j] * i <= M; ++j) {
int tmp = pri[j] * i;
isp[tmp] = true;
fac[tmp][0] = fac[i][0] * pri[j];
fac[tmp][1] = fac[i][1];
fac[tmp][2] = fac[i][2];
if (fac[tmp][0] > fac[tmp][1]) {
fac[tmp][0] ^= fac[tmp][1] ^= fac[tmp][0] ^= fac[tmp][1];
}
if (fac[tmp][1] > fac[tmp][2]) {
fac[tmp][1] ^= fac[tmp][2] ^= fac[tmp][1] ^= fac[tmp][2];
}
if (i % pri[j] == 0) {
break;
}
}
}
for (int i = 0; i <= T; ++i) {
pre[0][i] = pre[i][0] = i;
}
for (int i = 1; i <= T; ++i) {
for (int j = 1; j <= i; ++j) {
pre[i][j] = pre[j][i] = pre[j][i % j];
}
}
}
int gcd(int a, int b) {
int ans = 1;
for (int i = 0; i < 3; ++i) {
int tmp = (fac[a][i] > T) ?
(b % fac[a][i] ?
1
: fac[a][i]
)
: pre[fac[a][i]][b % fac[a][i]];
b /= tmp;
ans *= tmp;
}
return ans;
}
```