AcWing: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] 的最大公约数(GCD)。

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

输入格式

第一行两个整数N,M。

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

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

输出格式

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

每个答案占一行。

数据范围

N500000,M100000N≤500000,M≤100000

输入样例:

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

 

算法:线段树 + 增量数组(树状数组) + 差分序列

题解:

  性质:

  • gcd(a, b) = gcd(a, b - a)
  • gcd(a, b, c) = gcd(a, b - a, c - b)
  • acd(a1, a2, ... , an) = gcd(a1, a2 - a1, ... , an - an-1)

  利用这条性质来求解此题

  1. 对用询问“Q l r”来说,可以求出结果__gcd(arr[l], query(1, l + 1, r),就是同上面的性质,前面那个arr[l]就是性质里面的第一个数,后面的就是存在了线段树里面差分序列,求出(l + 1, r)区间的最大公约数即可。(其中的arr[l]等于原本数组里面的值加上后面更改的值,更改的值记录再树状数组里面)。
  2. 对于询问“C l r d”来说,只需要修改树状数组里面的值,以及线段树里面的值即可。

注意:题目会爆int,需要用long long。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

typedef long long ll;

const int maxn = 5e5+7;

struct node {
    ll l, r;
    ll dat;
}tree[maxn << 2];   //维护差分序列的线段树

ll n, m;
ll d[maxn];         //差分数组
ll arr[maxn];       //原始数组
ll T[maxn];         //增量数组(树状数组)

ll lowbit(ll x) {
    return x & (-x);
}

void pushup(ll root) {
    tree[root].dat = __gcd(tree[root << 1].dat, tree[root << 1 | 1].dat);
}

void build(ll root, ll l, ll r) {
    tree[root].l = l;
    tree[root].r = r;
    if(l == r) {
        tree[root].dat = d[l];
        return;
    }
    ll mid = (l + r) >> 1;
    build(root << 1, l, mid);
    build(root << 1 | 1, mid + 1, r);
    pushup(root);
} 

void add(ll x, ll val) {
    while(x <= n) {
        T[x] += val;
        x += lowbit(x);
    }
}

ll ask(ll x) {
    ll res = 0;
    while(x > 0) {
        res += T[x];
        x -= lowbit(x);
    }
    return res;
}

void update(ll root, ll pos, ll val)  {
    ll l = tree[root].l;
    ll r = tree[root].r;
    if(l == r) {
        tree[root].dat += val;
        return;
    }
    ll mid = (l + r) >> 1;
    if(pos <= mid) {
        update(root << 1, pos, val);
    } else {
        update(root << 1 | 1, pos, val);
    }
    pushup(root);
}

ll query(ll root, ll x, ll y) {
    ll l = tree[root].l;
    ll r = tree[root].r;
    if(x <= l && r <= y) {
        return tree[root].dat;
    }
    ll mid = (l + r) >> 1;
    ll res = 0;
    if(x <= mid) {
        res = __gcd(res, query(root << 1, x, y));
    }
    if(y > mid) {
        res = __gcd(res, query(root << 1 | 1, x, y));
    }
    return abs(res);        //注意:这里需要加绝对值,因为可能出现负数
}

int main() {
    scanf("%lld%lld", &n, &m);
    for(ll i = 1; i <= n; i++) {
        scanf("%lld", &arr[i]);
        d[i] = arr[i] - arr[i - 1];         //构建差分数组
    }
    build(1, 1, n);
    while(m--) {
        char str[5];
        ll l, r, val;
        scanf("%s", str);
        if(str[0] == 'Q') {
            scanf("%lld %lld", &l, &r);
            ll now = arr[l] + ask(l);   //获取当前位置的值(原始数组 + 增量数组)
            printf("%lld\n", __gcd(now, query(1, l + 1, r)));   //与后面的部分求最大公约数
        } else {
            scanf("%lld %lld %lld", &l, &r, &val);
            add(l, val);
            add(r + 1, -val);
            update(1, l, val);
            if(r < n) {     //判断是否会越界
                update(1, r + 1, -val);
            }
        
        }
    }
    return 0;
}

 

posted @ 2019-07-31 11:34  不会fly的pig  阅读(378)  评论(1编辑  收藏  举报