📅  最后修改于: 2023-12-03 14:50:06.985000             🧑  作者: Mango
在数学中,最大公约数(Greatest Common Divisor,简称 GCD)指的是几个整数的公共约数中最大的一个。如果两个整数的最大公约数为 1,则这两个整数互质,可以简写为 $GCD(a,b)=1$。在一个序列中,如果任意两个数的 GCD 都为 1,则该序列被称为具有单位 GCD。
现在我们来考虑一个问题:对于一个长度为 $n$ 的序列 $a_1,a_2,...,a_n$,我们想要找到一个长度最短的非空子序列,使得该子序列具有单位 GCD。那么,如何求解这个问题呢?
实际上,该问题有一个非常直观的暴力解法:枚举所有的非空子序列,对每个子序列计算其 GCD 是否为 1。如果该子序列的 GCD 为 1,则更新最小长度。这种做法的时间复杂度为 $O(n^3)$,显然无法满足实际的需求。
但是,我们可以使用一些技巧将时间复杂度优化至 $O(n \log^2 n)$。具体来说,我们考虑使用线性筛法快速计算出所有的质数,然后对于每个质数,我们找到所有能够被该质数整除的数,组成一个子序列。如果该子序列的长度大于该质数,则一定存在一个长度为该质数的非空子序列,该子序列的 GCD 为该质数。因此,我们可以直接更新最小长度。这种做法的正确性可以通过反证法得到证明。
具体实现细节可以参考下面的代码实现。
const int MAXN = 1e5 + 5;
int n, a[MAXN], vis[MAXN], prime[MAXN], tot;
int mn[MAXN], ans = INT_MAX;
void init() {
mn[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!vis[i]) {
prime[++tot] = i;
mn[i] = 1;
}
for (int j = 1; j <= tot && i * prime[j] <= n; ++j) {
vis[i * prime[j]] = true;
if (i % prime[j] == 0) {
mn[i * prime[j]] = mn[i];
break;
}
mn[i * prime[j]] = std::min(mn[i], prime[j]);
}
}
}
void solve() {
std::map<int, int> mp; mp.clear();
for (int i = 1; i <= n; ++i) {
if (a[i] == 1) {
ans = 1;
return;
}
int x = a[i] / mn[a[i]];
int tmp = mp[x];
if (tmp) {
ans = std::min(ans, i - tmp + 1);
}
mp[x] = tmp ? tmp : i;
}
}
int main() {
std::cin >> n;
for (int i = 1; i <= n; ++i) {
std::cin >> a[i];
}
init();
solve();
std::cout << ans << std::endl;
return 0;
}
上面的代码中,mn[i]
表示整数 $i$ 的最小质因子。我们可以使用线性筛法求出所有的最小质因子,并预处理出 $mn$ 数组。然后对于每个质数 $p$,我们可以找到所有能够被 $p$ 整除的数,组成一个子序列,并使用 std::map
维护子序列中的最早出现位置。最后遍历原序列,对于每个数,我们找到其对应的质因子 $p$,计算 $a_i / \text{mn}[a_i]$ 的值,并在 std::map
中查找是否存在相同值。若存在,则说明该子序列的长度不小于 $p$,并更新最小长度即可。
这个问题需要我们从数学和算法两个角度来考虑。虽然暴力解法不难想到,但是时间复杂度极高,无法通过本题。线性筛法则是一种非常常用的算法,可以快速计算所有的质数以及所有数的最小质因子。通过巧妙地利用线性筛法,我们可以将时间复杂度优化至 $O(n \log^2 n)$,达到了较好的效果。