📅  最后修改于: 2023-12-03 15:15:13.844000             🧑  作者: Mango
在数组中找到所有的连续子数组,并计算其中GCD等于1的子数组数目。这个问题看起来可能很难,但是对于数学背景稍微丰富的人来说,可以使用莫比乌斯反演来解决。
假设 $f$ 为从 $[1, n]$ 到某个集合的函数,那么 $g$ 满足 $g(m) = \sum_{d|m} f(d)$。莫比乌斯反演告诉我们,当 $n \leq x$ 时,$f(n) = \sum_{d|n} g(d) \mu(n/d)$,其中 $\mu$ 是莫比乌斯函数。注意到 $g(m)$ 其实就是说了 $f$ 的一个前缀和,那么我们可以用前缀和预处理 $g$,再通过莫比乌斯反演计算出 $f$。
假设数组为 $a$,长度为 $n$。我们需要计算的值是 $f(k) = \sum_{i=1}^{n} [ gcd(a_i, \cdots, a_{i+k-1}) = 1 ]$。求出每个 $g(k) =\sum_{d|k}[d = gcd(a_1, \cdots, a_{d-1})]$,然后根据莫比乌斯反演可以求出 $f$:$f(k) = \sum_{d|k} g(d) \mu(k/d)$。 具体实现在代码片段中给出。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int a[500010], n;
ll ans[500010], g[1000010];
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL); cout.tie(NULL);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) {
vector<int> V;
for(int j = i; j <= n; j++) {
V.push_back(a[j]);
if(a[j+1] > 1000000) break;
vector<int> divs;
for(int x = 1; x * x <= a[j]; x++) {
if(a[j] % x == 0) {
divs.push_back(x);
if(x * x != a[j]) divs.push_back(a[j] / x);
}
}
sort(divs.begin(), divs.end());
int mx = i;
for(int k = divs.size()-1, t = 0; k >= 0; k--) {
int x = divs[k];
while(a[mx] % x != 0 && mx < j) mx++;
if(mx == j) continue;
t++;
g[x] += (ll)(mx-i+1);
if(k) g[divs[k-1]] -= (ll)(mx-i+1);
}
for(auto &x : divs) g[x] = 0;
}
ans[1] += (ll)(n-i+1);
for(int x : V) ans[x] += g[x], g[x] = 0;
}
for(int i = 2; i <= n; i++) ans[i] += ans[i-1];
cout << ans[1];
for(int i = 2; i <= n; i++) cout << " " << ans[i] - ans[i-1];
cout << "\n";
return 0;
}
复杂度:$O(n \log n \log a_n)$。排序算法复杂度为 $O(n \log n)$,枚举因数和前缀和的时间复杂度为 $O(n \log a_n)$。