Appearance
下文,若不做特殊说明,均令
一般的 gcd 求法
辗转相除法(欧几里得算法)
单次询问时间复杂度:
记忆化,预处理时空复杂度:
更相减损术 + Stein
。 ,此时 。
时间复杂度:
优势是只涉及减和除以
在高精度意义下,时间复杂度为
注:但是由于其和二进制相关的特性,使用二进制相关操作实现时,常数非常小,实测效果优于后文中实现不好的“根号分治”gcd。
实现如下:
cpp
int gcd(int a, int b) {
if (!a | !b)
return a | b;
int az = __builtin_ctz(a);
int bz = __builtin_ctz(b);
int z = min(az, bz);
a >>= az, b >>= bz;
while (a != b) {
int diff = b - a;
az = __builtin_ctz(diff);
b = min(a, b), a = abs(diff) >> az;
}
return a << z;
}基于值域的 gcd 求法
令
分解质因数
令
线性筛预处理
求
综上,预处理时空复杂度:
“根号分治”
引理:
, 或 。
求出
具体求解过程为:
,其中 ,一般选取 的最小质因子即可。
使用线性筛可以
求
对于后三式,容易分讨:
,通过预处理 内的 ,调用即可。 , ,此时 ,调用即可。 ,可得
综上,预处理时空复杂度:
cpp
void primes(int n) {
is_prime.set();
is_prime[0] = is_prime[1] = 0;
for (int i = 4; i <= n; i += 2) {
is_prime[i] = 0;
minn[i] = 2;
}
for (int i = 3; i <= n / i; i++) {
if (!is_prime[i])
continue;
for (int j = i * i; j <= n; j += 2 * i) {
if (is_prime[j])
minn[j] = i;
is_prime[j] = 0;
}
}
for (int i = 2; i <= n; i++)
if (is_prime[i])
minn[i] = i;
x1[1] = x2[1] = x3[1] = 1;
for (int i = 2; i <= n; i++) {
if (is_prime[i]) {
x1[i] = x2[i] = 1;
x3[i] = i;
} else {
int now = i / minn[i];
tmp[0] = x1[now] * minn[i];
tmp[1] = x2[now];
tmp[2] = x3[now];
sort(tmp, tmp + 3);
x1[i] = tmp[0], x2[i] = tmp[1], x3[i] = tmp[2];
}
}
for (int i = 1; i <= sqt; i++) {
res[i][0] = res[0][i] = i;
for (int j = 1; j <= i; j++) {
res[i][j] = res[j][i] = res[j][i % j];
}
}
}
int calc(int x, int y) {
while (1) {
if (x > y)
swap(x, y);
else if (y <= sqt)
return res[x][y];
else if (x <= sqt) {
int z = y % x;
y = x, x = z;
} else if (y % x == 0)
return x;
else
return 1;
}
}
int gcd(int x, int y) {
int a = calc(x1[x], y);
int b = calc(x2[x], y / a);
int c = calc(x3[x], y / a / b);
return a * b * c;
}但是事实上,容易发现,这里把 if-else 分支。
实际上常数和“质因数分解”求法大概差两倍,更大的优势是空间严格线性。(虽然就算空间,在
gcd 的势能均摊
连续求 gcd 的均摊
也就是时间复杂度是
那么求解
综上,对于多个数连续求
由此得到的一个经典 trick 是:线段树维护区间
区间 gcd 的均摊
因为 gcd 相当于对质因子幂指数取
从后向前,
