📜  GCD = 1的子数组数|段树(1)

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

GCD = 1的子数组数 | 段树

介绍

本题要求计算数组中所有GCD = 1的子数组数量。其中GCD为最大公约数,子数组是指一个数组中任意选择连续的元素构成的子序列。

为了解决这个问题,我们可以利用数学中的性质和数据结构-线段树。具体地,对于一个数组,我们可以利用线段树维护出每个区间内的gcd值,并通过一些计算方法计算出所有GCD = 1的子数组数量。

算法流程
建立线段树

对于一个数组$A$,我们定义线段树节点$node_{l,r}$表示$A[l...r]$这个区间的gcd值。对于区间数量大于1的节点,我们可以通过递归构建线段树,由下至上进行区间gcd值的计算。

具体地,对于节点$node_{l,r}$,如果区间大小为一,则gcd值为$A[l]$。否则,我们递归建立$node_{l,mid}$和$node_{mid+1,r}$,然后计算$node_{l,r}$的gcd值,即$node_{l,mid}$和$node_{mid+1,r}$的gcd值。

struct Node {
    int l, r, gcd;
} tr[N << 2];
void build(int p, int l, int r, int a[]) {
    tr[p] = {l, r};
    if (l == r) {
        tr[p].gcd = a[l];
        return;
    }
    int mid = l + r >> 1;
    build(p << 1, l, mid, a), build(p << 1 | 1, mid + 1, r, a);
    tr[p].gcd = __gcd(tr[p << 1].gcd, tr[p << 1 | 1].gcd);
}
查询区间gcd值

对于一个区间$[l,r]$,我们可以通过线段树快速查询出它的gcd值。具体地,我们可以利用线段树节点的特性和gcd的性质,将区间分为三种情况进行分类讨论:

  • 区间$[l,r]$与目标区间$[L,R]$没有交集,返回1,即恒成立;
  • 区间$[l,r]$被包含在目标区间$[L,R]$内,返回节点$tr[p]$的gcd值;
  • 区间$[l,r]$与目标区间$[L,R]$相交或包含,递归查询子区间的gcd值,并返回它们的gcd值。
int query(int p, int l, int r, int L, int R) {
    if (L <= l && r <= R) return tr[p].gcd;
    int mid = l + r >> 1, res = 0;
    if (L <= mid) res = __gcd(res, query(p << 1, l, mid, L, R));
    if (R > mid) res = __gcd(res, query(p << 1 | 1, mid + 1, r, L, R));
    return res;
}
计算子数组数量

对于每一个区间$[l,r]$,我们可以将它转化为以$r$为结尾的所有子数组$[l,r]$。显然,区间$[l,r]$是所有以$r$为结尾的子数组的公共前缀。而只有当这个公共前缀的gcd值为1时,我们才能将这个公共前缀与$r$组合成一个满足题目条件的子数组。

具体地,我们可以逆序遍历数组$A$,对于每个元素$A[i]$,记$[1,i]$这个区间的gcd值为gcd,若gcd = 1,则说明以$i$结尾的所有子数组的前缀都是gcd = 1的。

我们可以使用线段树进行查询,查询区间$[1,i]$的gcd值。具体地,我们记录以$i$为结尾的gcd = 1的区间个数$res$,然后对于每个$A[i]$,计算出以$i$为结尾的子数组的数量,即$res \times (n-i+1)$。

for (int i = n; i; i--) {
    for (int j = 1; j <= pc && primes[j] <= a[i]; j++) {
        if (a[i] % primes[j] == 0) {
            for (int k = head[primes[j]]; k; k = next[k]) {
                if (idx[k] <= i) break;
                int tmp = cnt[lst[k]] - cnt[i + 1];
                ans += 1ll * tmp * (idx[k] - i) * (n - idx[k] + 1);
            }
            head[primes[j]] = i;
            lst[++pc] = i; idx[pc] = a[i];
            break;
        }
    }
}
总结

本题结合了数学、算法和数据结构的知识,难度相对较高。对于比较有经验的程序员和算法爱好者,建议收藏并深入学习一下。对于初学者,建议先从简单的算法和数据结构入手,逐步提升自己的能力,这样才能够更好地攀登技术高峰。