目录
题目-区间最大公约数

问题分析
两个操作
- 区间修改, [ A L , A R ] [A_L, A_R] [AL,AR]增加一个数字 x x x
- 求区间内的最大公约数
因为涉及到区间修改和区间查询, 使用线段树
线段树节点信息中需要存储如下信息
cpp
struct Node {
int l, r;
int gcd;
};
对于当前区间 u u u, 左儿子是 x x x, 右儿子是 y y y, 那么当前区间的最大公约数等于
u . g c d = gcd ( x . g c d , y . g c d ) u.gcd = \gcd(x.gcd, y.gcd) u.gcd=gcd(x.gcd,y.gcd)
因此, 如果只是查询 , 只存储一个最大公约数 d d d就可以
对于修改情况, 假设当前区间的最大公约数是
gcd ( x 0 , x 1 , x 2 , . . . , x n ) \gcd (x_0, x_1, x_2, ..., x_n) gcd(x0,x1,x2,...,xn)
区间修改 后的最大公约数是
gcd ( x 0 + x , x 1 + x , . . . , x n + x ) \gcd(x_0 + x, x_1 + x, ..., x_n + x) gcd(x0+x,x1+x,...,xn+x)
发现无法推导出两个最大公约数的操作
尝试将区间修改变为单点修改 , 发现因为一个数的 g c d gcd gcd等于它自己, 也就是 gcd ( x ) = x \gcd(x) = x gcd(x)=x
因此如果是直接修改一个位置, 向上递归的过程就会同时更新上面区间的最大公约数, 是可以直接操作的
因此可以尝试将原序列变为差分序列 , 对于当前区间情况
gcd ( x 0 , x 1 , x 2 , . . . , x n ) \gcd (x_0, x_1, x_2, ..., x_n) gcd(x0,x1,x2,...,xn)
有如下引理
gcd ( x 0 , x 1 , x 2 , . . . , x n ) = gcd ( x 0 , x 1 − x 0 , x 2 − x 1 , . . . , x n − x n − 1 ) \gcd (x_0, x_1, x_2, ..., x_n) = \gcd(x_0, x_1 - x_0, x_2 - x_1, ..., x_n - x_{n - 1}) gcd(x0,x1,x2,...,xn)=gcd(x0,x1−x0,x2−x1,...,xn−xn−1)
首先证明 gcd ( a , b ) = gcd ( a , b − a ) \gcd(a, b) = \gcd(a, b - a) gcd(a,b)=gcd(a,b−a), 并且因为最大公约数具有结合律, 因此可以直接推广到上面情况
证明:
设 d = gcd ( a , b ) d = \gcd(a, b) d=gcd(a,b), 因为求最大公约数-欧几里得算法的证明和实现有引理
d ∣ a d \; | \; a d∣a并且 d ∣ b d \; | \; b d∣b, 那么 x , y , ∈ Z x, y, \in Z x,y,∈Z情况下, d ∣ a x + b y d \; | \; ax + by d∣ax+by
因此令 x = 1 , y = − 1 x = 1, y = -1 x=1,y=−1, 有 a x + b y = a − b ax + by = a - b ax+by=a−b, 并且 d ∣ a d \; | \; a d∣a, 左侧能推出右侧
再将右侧展开
d ∣ a x + ( a − b ) y d \; | \; ax + (a - b)y d∣ax+(a−b)y
整理得到
d ∣ a ⋅ ( x + y ) − b y d \; | \; a \cdot (x + y) - by d∣a⋅(x+y)−by
令 x = 1 , y = − 1 x = 1, y = -1 x=1,y=−1
得到
d ∣ b d \; | \; b d∣b
因为 d ∣ a d \; | \; a d∣a, 因此右侧能推出左侧
因为左边能推出右边, 并且右边能推出左边, gcd ( a , b ) = gcd ( a , b − a ) \gcd(a, b) = \gcd(a, b - a) gcd(a,b)=gcd(a,b−a), 证毕
设 d = gcd ( x 0 , x 1 , x 2 , ... , x n ) d = \gcd(x_0, x_1, x_2, \dots, x_n) d=gcd(x0,x1,x2,...,xn)
(1) 左边整除右边
因为 d d d 整除每个 x i x_i xi,所以
d ∣ x 0 , d ∣ x 1 ⇒ d ∣ ( x 1 − x 0 ) d \mid x_0, \quad d \mid x_1 \quad\Rightarrow\quad d \mid (x_1 - x_0) d∣x0,d∣x1⇒d∣(x1−x0),同样, d ∣ x 2 − x 1 d \mid x_2 - x_1 d∣x2−x1,依此类推直到 x n − x n − 1 x_n - x_{n-1} xn−xn−1
所以 d d d 是 x 0 , x 1 − x 0 , x 2 − x 1 , ... , x n − x n − 1 x_0, x_1 - x_0, x_2 - x_1, \dots, x_n - x_{n-1} x0,x1−x0,x2−x1,...,xn−xn−1 的一个公因数
因此
d ∣ gcd ( x 0 , x 1 − x 0 , x 2 − x 1 , ... , x n − x n − 1 ) d \mid \gcd(x_0, x_1 - x_0, x_2 - x_1, \dots, x_n - x_{n-1}) d∣gcd(x0,x1−x0,x2−x1,...,xn−xn−1)(2) 右边整除左边
设 d ′ = gcd ( x 0 , x 1 − x 0 , x 2 − x 1 , ... , x n − x n − 1 ) d' = \gcd(x_0, x_1 - x_0, x_2 - x_1, \dots, x_n - x_{n-1}) d′=gcd(x0,x1−x0,x2−x1,...,xn−xn−1)
由 d ′ ∣ x 0 d' \mid x_0 d′∣x0,以及 d ′ ∣ ( x 1 − x 0 ) d' \mid (x_1 - x_0) d′∣(x1−x0),可得 d ′ ∣ x 1 d' \mid x_1 d′∣x1(因为 x 1 = ( x 1 − x 0 ) + x 0 x_1 = (x_1 - x_0) + x_0 x1=(x1−x0)+x0)
由 d ′ ∣ x 1 d' \mid x_1 d′∣x1,及 d ′ ∣ ( x 2 − x 1 ) d' \mid (x_2 - x_1) d′∣(x2−x1),可得 d ′ ∣ x 2 d' \mid x_2 d′∣x2(因为 x 2 = ( x 2 − x 1 ) + x 1 x_2 = (x_2 - x_1) + x_1 x2=(x2−x1)+x1)
依此类推,归纳可得 d ′ d' d′ 整除所有 x i x_i xi,即 d ′ ∣ gcd ( x 0 , x 1 , ... , x n ) = d d' \mid \gcd(x_0, x_1, \dots, x_n) = d d′∣gcd(x0,x1,...,xn)=d
由 d ∣ d ′ d \mid d' d∣d′ 和 d ′ ∣ d d' \mid d d′∣d 得 d = d ′ d = d' d=d′,即
gcd ( x 0 , x 1 , ... , x n ) = gcd ( x 0 , x 1 − x 0 , x 2 − x 1 , ... , x n − x n − 1 ) \gcd(x_0, x_1, \dots, x_n) = \gcd(x_0, x_1 - x_0, x_2 - x_1, \dots, x_n - x_{n-1}) gcd(x0,x1,...,xn)=gcd(x0,x1−x0,x2−x1,...,xn−xn−1)证毕
因此定义差分序列 b i = a i − a i − 1 b_i = a_i - a_{i - 1} bi=ai−ai−1, 因此可以将对区间的查询和修改转化为如下形式
f ( L , R ) = gcd ( a l , gcd ( b l + 1 ∼ b r ) ) f(L, R) = \gcd(a_l, \gcd(b_{l + 1} \sim b_r)) f(L,R)=gcd(al,gcd(bl+1∼br))
对于 gcd ( b l + 1 ∼ b r ) \gcd(b_{l + 1} \sim b_r) gcd(bl+1∼br), 区间查询 g c d gcd gcd, 单点修改值(因为改的是差分数组)
只需要修改 b r + 1 + x , b r − x b_{r + 1} + x, b_r - x br+1+x,br−x
对于 a l a_l al, 因为是差分数组, 查询的时候需要计算前缀和 , 修改也是区间修改, 但是因为转化为了差分数组 , 可以实现单点修改 , 可以使用树状数组或者线段树实现
算法步骤
代码实现
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 500010;
int n, m;
LL a[N], b[N];
struct Node {
int l, r;
LL d;
} tr[N << 2];
LL fwt[N];
int lowbit(int x) {
return x & -x;
}
LL gcd(LL a, LL b) {
return b ? gcd(b, a % b) : a;
}
void add(int u, LL val) {
for (int i = u; i <= n; i += lowbit(i)) fwt[i] += val;
}
LL get(int u) {
LL ans = 0;
for (int i = u; i; i -= lowbit(i)) ans += fwt[i];
return ans;
}
void pushup(int u) {
tr[u].d = gcd(tr[u << 1].d, tr[u << 1 | 1].d);
}
void build(int u, int l, int r) {
if (l == r) {
tr[u] = {l, r, b[l]};
return;
}
tr[u] = {l, r, 0};
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
void modify(int u, int x, LL val) {
if (tr[u].l == tr[u].r) {
tr[u].d = val;
return;
}
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) modify(u << 1, x, val);
else modify(u << 1 | 1, x, val);
pushup(u);
}
LL query(int u, int ql, int qr) {
if (tr[u].l >= ql && tr[u].r <= qr) return tr[u].d;
int mid = tr[u].l + tr[u].r >> 1;
LL ans = 0;
if (ql <= mid) ans = gcd(ans, query(u << 1, ql, qr));
if (qr > mid) ans = gcd(ans, query(u << 1 | 1, ql, qr));
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
b[i] = a[i] - a[i - 1];
add(i, b[i]);
}
build(1, 1, n);
while (m--) {
char c;
cin >> c;
if (c == 'C') {
int l, r;
LL val;
cin >> l >> r >> val;
add(l, val);
if (r + 1 <= n) add(r + 1, -val);
b[l] += val;
modify(1, l, b[l]);
if (r + 1 <= n) {
b[r + 1] -= val;
modify(1, r + 1, -b[r + 1]);
}
}
else {
int l, r;
cin >> l >> r;
LL val1 = get(l);
LL val2 = l < r ? query(1, l + 1, r) : 0;
cout << abs(gcd(val1, val2)) << '\n';
}
}
return 0;
}