线段树(区间最大公约数&区间修改&差分)

博客围绕一个数列问题展开,给定长度为N的数列A和M条指令,指令分两种,一是区间加值,二是询问区间最大公约数。题解利用最大公约数性质,用线段树维护差分数组的最大公约数和区间和,通过特定公式求取结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:https://www.acwing.com/problem/content/247/

题意:给定一个长度为 N 的数列 A,以及 M 条指令,总共两种情况:
1、C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
2、Q l r,表示询问 A[l],A[l+1],…,A[r] 的最大公约数(GCD)。
对于每个询问输出一个值
N≤500000,M≤100000, 1≤A[i]≤1018, |d|≤1018,不会超longlong

题解:最大公约数支持交换律和结合律,所以我可以用两个子区间的gcd再取个gcd就是我这整个区间的gcd,所以如果只有第2个操作的话,直接用线段树来维护gcd就可以了。
最大公约数还有一个性质gcd(x,y,z)=gcd(x,y-x,z-y)=k , 但gcd(x,y,z)和gcd(x+d,y+d,z+d)没有关系。根据这个性质,我们可以用线段树来维护差分数组的最大公约数,这样区间增加一个数,就可以利用差分数组变成单点修改了,然后线段树中再记录一个区间和,到时候1-l这个区间的区间和就是现在的a[l]了,因为差分数组的前缀和就是我没有差分之前的数,维护好这两个数之后,求l-r的最大公约数,就可以用gcd( a[l] , gcd( b[l+1] ~ b[r] ) )来求取结果。

在这里插入图片描述

#include <bits/stdc++.h>
//#define int long long
#define pb push_back
#define pii pair<int, int>
#define mpr make_pair
#define ms(a, b) memset((a), (b), sizeof(a))
#define x first
#define y second
typedef long long ll;
typedef unsigned long long LL;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
using namespace std;
inline int read() {
    char ch = getchar();
    int s = 0, w = 1;
    while (ch < '0' || ch > '9') {
        if (ch == '-') w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        s = s * 10 + ch - '0', ch = getchar();
    }
    return s * w;
}
const int N = 1e6 + 9;

int n, m;
ll w[N];
struct node {
    int l, r;  
    ll sum, d; //sum存区间和,到时候询问1-l之间的sum,就是我现在的w[l]
                //d存区间gcd
} tr[N * 4];

ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }

//用两个子节点来计算我这个父节点的值
void pushup(node &u, node &l, node &r) {
    u.sum = l.sum + r.sum; //我这一段区间sum和,就是两个字节点的
    u.d = gcd(l.d, r.d); //我这一段的最大公约数,就是两端区间分别的最大公约数再取个gcd
}
void pushup(int u) { pushup(tr[u], tr[u << 1], tr[u << 1 | 1]); }

//建树
void build(int u, int l, int r) {
    if (l == r) {//叶子节点
        ll b = w[r] - w[r - 1]; //求出差分
        tr[u] = {l, r, b, b};  //存差分值
        return;
    } 
    //往下找,并pushup求出非叶子节点的数据 
    tr[u].l = l, tr[u].r = r;
    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 v) {
    if (tr[u].l == x && tr[u].r == x) {
        ll b = tr[u].sum + v;
        tr[u] = {x, x, b, b};
    } else {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid)
            modify(u << 1, x, v);
        else
            modify(u << 1 | 1, x, v);
        pushup(u);
    }
}
//和上一道题一样,查询的时候因为是跨的不是线段树本来分割好的父区间与子区间
//所以自己要创造一个父区间res,让查询到的符合if的情况的区间,来更新我这个父区间
node query(int u, int l, int r) {
    if (tr[u].l >= l && tr[u].r <= r)
        return tr[u];
    else {
        int mid = tr[u].l + tr[u].r >> 1;
        if (r <= mid)
            return query(u << 1, l, r);
        else if (l > mid)
            return query(u << 1 | 1, l, r);
        else {
            auto left = query(u << 1, l, r);
            auto right = query(u << 1 | 1, l, r);
            node res;
            pushup(res, left, right);
            return res;
        }
    }
}
signed main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%lld", &w[i]);
    build(1, 1, n);
    int l, r;
    ll d;
    char op[2];
    while (m--) {
        scanf("%s%d%d", op, &l, &r);
        if (*op == 'Q') {
            node left = query(1, 1, l);   //询问1-l,主要是为了求left.sum,这个sum就是现在的w[i]
            node right = {0, 0, 0, 0};    //l+1>r的情况
            if (l + 1 <= r) right = query(1, l + 1, r);  //求出l+1到r之间的差分的最大公约数
            printf("%lld\n", abs(gcd(left.sum, right.d)));
        } else {
            scanf("%lld", &d);
            modify(1, l, d);  //线段树维护的是差分数组,所以变成单点修改
            if (r + 1 <= n) modify(1, r + 1, -d);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值