246. 区间最大公约数——差分+线段树

本文介绍了一种使用线段树和差分数组解决区间加法及查询最大公约数(GCD)问题的方法。通过差分转换,将区间加法变为单点修改,并利用线段树高效地进行区间查询。

给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:

C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
Q l r,表示询问 A[l],A[l+1],…,A[r] 的最大公约数(GCD)。
对于每个询问,输出一个整数表示答案。

输入格式
第一行两个整数 N,M。

第二行 N 个整数 A[i]。

接下来 M 行表示 M 条指令,每条指令的格式如题目描述所示。

输出格式
对于每个询问,输出一个整数表示答案。

每个答案占一行。

数据范围
N≤500000,M≤100000,
1≤A[i]≤1018,
|d|≤1018
输入样例:
5 5
1 3 5 7 9
Q 1 5
C 1 5 1
Q 1 5
C 3 3 6
Q 2 4
输出样例:
1
2
4

分析

  1. 一看是区间修改,可能就要懒标记,但是这题是区间加上一个数,那么可以利用差分转换成单点修改问题;那么线段树维护a的差分数组;区间查询的是最大公约数,然后最大公约数有一个性质:gcd(a1,a2,a3…,an)=gcd(a1,a2-a1,a3-a2…,an-an-1)
  2. 然后利用这个性质,可以把求区间[L,R]的最大公约数:gcd(a[L],gcd(a[L+1],a[L+2],…a[R])),转化为gcd(a[L],gcd(b[L+1],b[L+2],…b[R])),其中**a[L]**可以通过差分数组求前缀和求得(b[1]+…+b[L])(不能直接用输入时的a[L],因为有修改操作,原数组以及物是人非了),通过对线段树的[1,L]求前缀和即可;
  3. 不能直接query [L,R]的node,直接输出d,因为线段树维护的是差分数组的gcd值,所以我们要通过上面的转换,分两步求;
  4. 需要注意越界问题和abs问题:主函数,在查询、修改时,涉及到L+1、r+1的时候都要特判一下,具体见代码;还要注意输出答案的时候加一个绝对值,因为区间修改可能造成某个结点的d为负数,把结果加上绝对值即可;可参考:AcWing 246. 区间最大公约数(线段树 差分 辗转相除)
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 500010;

struct node {
    int l, r;
    LL sum, d; //d为最大公约数
};

int n, m;
LL a[N];
node t[N * 4];//维护a的差分

LL gcd(LL a, LL b) {
    return b == 0 ? a : gcd(b, a % b);
}

//儿子更新父亲
void pushup(node &u, node &l, node &r) {
    u.sum = l.sum + r.sum;
    u.d = gcd(l.d, r.d);
}

void pushup(int u) {
    pushup(t[u], t[u << 1], t[u << 1 | 1]);
}

void build(int u, int l, int r) {
    t[u] = {l, r};
    //找到叶子
    if (l == r) {
        //本题有用于建立线段树的初始数组a
        t[u].sum = a[l] - a[l - 1];//差分
        t[u].d = a[l] - a[l - 1];
        return;
    }
    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 (t[u].l == x && t[u].r == x) {
        //t[u].sum=t[u].d=v; 不能这样写,因为区间加一个v,不是修改成v
        t[u].sum = t[u].sum + v;
        t[u].d = t[u].d + v;
        return;
    }
    //找叶子
    int mid = t[u].l + t[u].r >> 1;
    if (x <= mid)
        modify(u << 1, x, v);
    else
        modify(u << 1 | 1, x, v);
    //更新父亲
    pushup(u);
}

node query(int u, int l, int r) {
    //1. 完全包含
    if (l <= t[u].l && r >= t[u].r)
        return t[u];
    int mid = t[u].l + t[u].r >> 1;
    //2.在左边
    if (r <= mid)
        return query(u << 1, l, r);
        //3.在右边
    else if (l >= mid + 1)
        return query(u << 1 | 1, l, r);
        //4.两边都有
    else {
        auto left = query(u << 1, l, r);
        auto right = query(u << 1 | 1, l, r);
        node res;
        pushup(res, left, right);
        return res;
    }
}


int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    build(1, 1, n);

    while (m--) {
        char op;
        int l, r;
        LL d;
        cin >> op >> l >> r;
        if (op == 'Q') {
        	//维护[1,L]的结点
            node left = query(1, 1, l);
            //维护[L+1,R]的结点
            node right = {0, 0, 0, 0};
            if (l + 1 <= n)//当l==r==n时候,l+1就越界了
                right = query(1, l + 1, r);
            cout << abs(gcd(left.sum, right.d)) << endl;
            //线段树维护的是b的区间最大公约数,要把所求的a,转换为和b相关的
            //转化为gcd(a[L],gcd(b[L+1],b[L+2],....b[R])),也就是上面所输出的
            //cout << abs(query(1, l, r).d) << endl;
        } else {
            cin >> d;
            //区间+d: b[l]+d ,b[r+1]-d
            modify(1, l, d);
            if (r + 1 <= n)//特判
                modify(1, r + 1, -d);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

向上的yyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值