📜  GCD等于1的子阵列数(1)

📅  最后修改于: 2023-12-03 15:15:13.844000             🧑  作者: Mango

GCD等于1的子阵列数

介绍

在数组中找到所有的连续子数组,并计算其中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)$。