题目-区间最大公约数

在这里插入图片描述

问题分析

两个操作

  • 区间修改, [ A L , A R ] [A_L, A_R] [AL,AR]增加一个数字 x x x
  • 求区间内的最大公约数

因为涉及到区间修改和区间查询, 使用线段树
线段树节点信息中需要存储如下信息

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,x1x0,x2x1,...,xnxn1)

首先证明 gcd ⁡ ( a , b ) = gcd ⁡ ( a , b − a ) \gcd(a, b) = \gcd(a, b - a) gcd(a,b)=gcd(a,ba), 并且因为最大公约数具有结合律, 因此可以直接推广到上面情况

证明:
d = gcd ⁡ ( a , b ) d = \gcd(a, b) d=gcd(a,b), 因为求最大公约数-欧几里得算法的证明和实现有引理

d    ∣    a d \; | \; a da并且 d    ∣    b d \; | \; b db, 那么 x , y , ∈ Z x, y, \in Z x,y,Z情况下, d    ∣    a x + b y d \; | \; ax + by dax+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=ab, 并且 d    ∣    a d \; | \; a da, 左侧能推出右侧
再将右侧展开
d    ∣    a x + ( a − b ) y d \; | \; ax + (a - b)y dax+(ab)y
整理得到
d    ∣    a ⋅ ( x + y ) − b y d \; | \; a \cdot (x + y) - by da(x+y)by
x = 1 , y = − 1 x = 1, y = -1 x=1,y=1
得到
d    ∣    b d \; | \; b db
因为 d    ∣    a d \; | \; a da, 因此右侧能推出左侧
因为左边能推出右边, 并且右边能推出左边, gcd ⁡ ( a , b ) = gcd ⁡ ( a , b − a ) \gcd(a, b) = \gcd(a, b - a) gcd(a,b)=gcd(a,ba), 证毕

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) dx0,dx1d(x1x0)
同样, d ∣ x 2 − x 1 d \mid x_2 - x_1 dx2x1,依此类推直到 x n − x n − 1 x_n - x_{n-1} xnxn1
所以 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,x1x0,x2x1,,xnxn1 的一个公因数
因此
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}) dgcd(x0,x1x0,x2x1,,xnxn1)
(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,x1x0,x2x1,,xnxn1)
d ′ ∣ x 0 d' \mid x_0 dx0,以及 d ′ ∣ ( x 1 − x 0 ) d' \mid (x_1 - x_0) d(x1x0),可得 d ′ ∣ x 1 d' \mid x_1 dx1(因为 x 1 = ( x 1 − x 0 ) + x 0 x_1 = (x_1 - x_0) + x_0 x1=(x1x0)+x0
d ′ ∣ x 1 d' \mid x_1 dx1,及 d ′ ∣ ( x 2 − x 1 ) d' \mid (x_2 - x_1) d(x2x1),可得 d ′ ∣ x 2 d' \mid x_2 dx2(因为 x 2 = ( x 2 − x 1 ) + x 1 x_2 = (x_2 - x_1) + x_1 x2=(x2x1)+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 dgcd(x0,x1,,xn)=d
d ∣ d ′ d \mid d' dd d ′ ∣ d d' \mid d dd 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,x1x0,x2x1,,xnxn1)
证毕

因此定义差分序列 b i = a i − a i − 1 b_i = a_i - a_{i - 1} bi=aiai1, 因此可以将对区间的查询和修改转化为如下形式
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+1br))

对于 gcd ⁡ ( b l + 1 ∼ b r ) \gcd(b_{l + 1} \sim b_r) gcd(bl+1br), 区间查询 g c d gcd gcd, 单点修改值(因为改的是差分数组)
只需要修改 b r + 1 + x , b r − x b_{r + 1} + x, b_r - x br+1+x,brx

对于 a l a_l al, 因为是差分数组, 查询的时候需要计算前缀和, 修改也是区间修改, 但是因为转化为了差分数组, 可以实现单点修改, 可以使用树状数组或者线段树实现

算法步骤

代码实现

#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;
}