P10463 Interval GCD

P10463 Interval GCD

原题传送门

思路

首先,有个性质:对于任意多整数,它们的最大公约数与它们的差分序列的最大公约数相同,可以通过以下证明。

\(\forall a, b, c \in \mathbb{N} \text{,有} \gcd(a, b, c) = \gcd(a, b - a, c - b)\)
\(\text{证明:设} d \mid a, d \mid b, d \mid c\)
\(a = k_1 d, b = k_2 d, c = k_3 d, (k_1, k_2, k_3 \in \mathbb{N})\)
\(\because b - a = (k_2 - k_1)d, c - b = (k_3 - k_2)d\)
\(\therefore d \mid {b - a}, d \mid {c - b}\)

易发现,把 \(3\) 个数拓展到任意多整数,该性质都是成立的。

所以可以构造 \(A\) 的差分序列 \(B\),用线段树来维护 \(B\) 的区间最大公约数,询问区间最大公约数就可以通过求 \(A_l\)\(B\)\(l + 1\)\(r\) 区间的最大公约数来解决。

这样,在执行操作 1 时,就只用让 \(B_l + d\)\(B_{r + 1} - d\) ,不会改变中间的数,也就不用把区间中每个数都再取出来求最大公约数了!

此外因为询问需要用到数列 \(A\) 中的值,可以用树状数组来维护 \(A\) 中的数值。

有一点需要注意,差分序列的可能有负数,所以询问时要取绝对值

代码

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

using namespace std;

const int N = 5e5 + 5;

#define ll long long
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define mid ((l + r) >> 1)
#define LF(i, __l, __r) for (int i = __l; i <= __r; i++)

int n, m;
ll a[N], tr[N << 2], b[N], c[N];

ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a;}
void up(int rt) { tr[rt] = gcd(tr[ls], tr[rs]); }

void build(int rt, int l, int r) {
    if (l == r) { tr[rt] = b[l]; return; }
    build(ls, l, mid), build(rs, mid + 1, r);
    up(rt);
}

void update(int rt, int l, int r, int x, ll v) {
    if (l == r){ tr[rt] += v; return; }
    if (x <= mid) update(ls, l, mid, x, v);
    else update(rs, mid + 1, r, x, v);
    up(rt);
}

ll query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return abs(tr[rt]);
    ll val = 0;
    if (mid >= L) val = gcd(val, query(ls, l, mid, L, R));
    if (mid < R) val = gcd(val, query(rs, mid + 1, r, L, R));
    return abs(val);
}

ll sum(int x) {
    ll y = 0;
    for (; x; x -= x & -x) y += c[x];
    return y;
}

void add(int x, ll y) { for (; x <= n; x += x & -x) c[x] += y; }

int main()
{
    scanf("%d%d", &n, &m);
    LF(i, 1, n) {
        scanf("%lld", &a[i]);
        b[i] = a[i] - a[i - 1];
    }

    build(1, 1, n);

    LF(i, 1, m) {
        int l, r;
        char s[2];
        scanf("%s%d%d", s, &l, &r);

        if (s[0] == 'Q') {
            ll al = a[l] + sum(l);
            ll val = l < r ? query(1, 1, n, l + 1, r) : 0;
            printf("%lld\n", gcd(al, val));
        }
        else {
            ll d;
            scanf("%lld", &d);
            update(1, 1, n, l, d);
            if (r < n) update(1, 1, n, r + 1, -d);
            add(l, d);
            add(r + 1, -d);
        }
    }
    return 0;
}
posted @ 2024-07-27 22:43  FRZ_29  阅读(19)  评论(0编辑  收藏  举报