📅  最后修改于: 2023-12-03 15:15:13.807000             🧑  作者: Mango
本题要求计算数组中所有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);
}
对于一个区间$[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;
}
}
}
本题结合了数学、算法和数据结构的知识,难度相对较高。对于比较有经验的程序员和算法爱好者,建议收藏并深入学习一下。对于初学者,建议先从简单的算法和数据结构入手,逐步提升自己的能力,这样才能够更好地攀登技术高峰。