Appearance
下文,若不做特殊说明,均令
一般的 gcd 求法
辗转相除法(欧几里得算法)
单次询问时间复杂度:
记忆化,预处理时空复杂度:
更相减损术 + Stein
。 ,此时 。
时间复杂度:
优势是只涉及减和除以
对于有符号整数,应先用更宽的有符号类型取得绝对值,再转换为无符号整数执行减法和移位。下面的实现返回 uint64_t,因为 int 表示。
在高精度意义下,时间复杂度为
注:但是由于其和二进制相关的特性,使用二进制相关操作实现时,常数非常小,实测效果优于后文中实现不好的“根号分治”gcd。
实现如下:
点击展开代码
cpp
uint64_t gcd(int a, int b) {
auto magnitude = [](int x) -> uint64_t {
return x < 0 ? uint64_t(-int64_t(x)) : uint64_t(x);
};
uint64_t x = magnitude(a), y = magnitude(b);
if (!x || !y)
return x | y;
int az = __builtin_ctzll(x);
int bz = __builtin_ctzll(y);
int z = min(az, bz);
x >>= az, y >>= bz;
while (x != y) {
if (x > y)
swap(x, y);
y -= x;
y >>= __builtin_ctzll(y);
}
return x << 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 的均摊
欧几里得算法的一次迭代未必让新的较大数减半,但连续两次迭代后,较大数一定至少减半,因此单次
连续计算多个数的 gcd 时,可以使用势能分析。令当前累计答案依次为
在通常把定长整数 gcd 视为常数时间操作的模型下,标准线段树 build 只访问
区间 gcd 的均摊
因为 gcd 相当于对质因子幂指数取
从后向前,
