进阶数据结构应用-区间最大公约数

目录

题目-区间最大公约数

问题分析

两个操作

  • 区间修改, [ 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;
}
相关推荐
( •̀∀•́ )9201 小时前
高性能拖拽排序
java·开发语言·算法
高级盘丝洞2 小时前
如何通过Powerlink协议读取PLC数据
开发语言·数据库·php
在人间负债2 小时前
昇腾 RAG SDK 从入门到实战:技术解析与部署实操
后端·算法
Yang-Never2 小时前
Open GL ES->EGL渲染环境、数据、引擎、线程的创建
android·java·开发语言·kotlin·android studio
unicrom_深圳市由你创科技2 小时前
使用 Vue3 + Nest.js 构建前后端分离项目的完整指南
开发语言·javascript·状态模式
大千AI助手2 小时前
多维空间的高效导航者:KD树算法深度解析
数据结构·人工智能·算法·机器学习·大千ai助手·kd tree·kd树
我叫张小白。2 小时前
Vue3 v-model:组件通信的语法糖
开发语言·前端·javascript·vue.js·elementui·前端框架·vue
翻斗花园牛图图-2 小时前
Qt开发——系统相关3(Qt网络编程)
开发语言·qt
凋零蓝玫瑰2 小时前
几何:数学世界的空间密码
人工智能·算法·机器学习