[线段树] AcWing-246 区间最大公约数

246. 区间最大公约数

题目

        给定一个长度为 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] 的最大公约数\left (GCD \right )

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

思路

        首先我们需要先解决如何维护数据的问题,具体来说我们需要一个能够在一个区间下维护公约数性质的数据结构。

        根据《九章算术》,我们知道 gcd (a, b) = gcd (a, b-a);同理我们可以推导一下发现 gcd (a, b, c) = gcd (a, b-a, c-b) (例如:gcd (4, 6, 12) = gcd (4, 2, 6)),同时这个性质对任意多整数都成立。那么我们使用线段树来完成:维护线段树Mem,使得每一个节点代表着一个区间的最大公约数。这个线段树的叶节点 p 代表 A[p] - A[p-1],这样我们可以利用 gcd 的性质将区间修改转化为单点修改。此时查询命令变为 gcd(A[l], query(1, l+1, r));修改变为:change(1, l, d) 和change(1, r+1, -d)。

        对于数组A,我们可以使用“区间修改,单点查询”的树状数组维护。

代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define MAXN 500005

long long gcd(ll a, ll b) {
    while (a != 0 && b != 0) {
        ll t = a;
        a = b % t;
        b = t;
    }
    return a + b;
}

int n, m, N=MAXN;

ll a[MAXN];
ll treemem[MAXN]; //树状数组维护的是每一次更新的内容
int lowbit (int x) {
    return x & (-x);
} //找到x二进制下等于1的最低位
void update (int l, int r, ll val) { //l到r区间加val转化为两次单点修改
    for (int i=l; i<=N; i+=lowbit(i)) {
        treemem[i] += val;
    }
    for (int i=r+1; i<=N; i+=lowbit(i)) {
        treemem[i] -= val;
    }
}
ll query (int pos) { //取出pos位置的值
    ll ans = a[pos];
    for (int i=pos;i>0;i-=lowbit(i)) {
        ans+=treemem[i];
    }
    return ans;
}

struct Mem { //线段树
    int l, r;
    ll val;
} mem[MAXN*4];

void UpdateSegtree (int p) { //每个节点记录的都是最大公约数
    mem[p].val = gcd(mem[p*2].val, mem[p*2+1].val);
}

void BuildSegtree (int p, int l, int r) { //建立线段树
    mem[p].l = l; mem[p].r = r;
    if (mem[p].l == mem[p].r) {
        mem[p].val = a[l] - a[l-1]; //线段树的叶节点值为A数组两项之差
        return;
    }
    int mid = (l+r)/2;
    BuildSegtree(p*2, l, mid);
    BuildSegtree(p*2+1, mid+1, r);
    UpdateSegtree(p);
}

ll QuerySegtree (int p, int l, int r) { //查询函数
    if (l<=mem[p].l && mem[p].r<=r) { return mem[p].val; }
    int mid = (mem[p].l+mem[p].r)/2;
    if (r <= mid) {
        return QuerySegtree(p*2, l, r);
    }
    if (mid < l) {
        return QuerySegtree(p*2+1, l, r);
    }
    return gcd(QuerySegtree(p*2, l, mid), QuerySegtree(p*2+1, mid+1, r));
}

void PchangeSegtree (int p, int x, ll v) { //单点修改
    if (mem[p].l == mem[p].r) {
        mem[p].val += v;
        return;
    }
    int mid = (mem[p].l+mem[p].r)/2;
    if (x <= mid) PchangeSegtree(p*2, x, v);
    else PchangeSegtree(p*2+1, x, v);
    UpdateSegtree(p);
}

int main () {
    while (scanf("%d %d", &n, &m) != EOF) {
        for (int i=1; i<=n; i++) {
            scanf("%lld", &a[i]);
        }
        BuildSegtree(1, 1, n+1);
        for (int i=0; i<m; i++) {
            char inps[2];
            int x, y;
            ll z;
            scanf("%s", inps);
            if (inps[0] == 'Q') {
                scanf("%d %d", &x, &y);
                if (x == y) printf("%lld\n", abs(query(x)));
                else printf("%lld\n", abs(gcd(query(x), QuerySegtree(1, x+1, y))));
            }
            else {
                scanf("%d %d %lld", &x, &y, &z);
                PchangeSegtree(1, x, z); //对线段树的修改
                PchangeSegtree(1, y+1, -z); //因为类似差分数列,所以只修改两个位置的值
                update(x, y, z); //对树状数组的修改
            }
        }
    }
}

        上传前尝试过了,能Accept。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值