NC 26255 更相减损术 + 线段树

题意

传送门 NC 26255

题解

只考虑后两种操作很容易用线段树维护,但第一种操作需要进行区间修改,复杂度可能退化为 O ( n ) O(n) O(n)。考虑差分处理。差分的好处在于将区间更新变为单点更新,求单点原值的时候 O ( l o g n ) O(logn) O(logn) 求前缀和即可。问题在于如何维护区间的最大公约数。

根据更相减损术(类似于减法版的辗转相除法),最大公约数有如下性质 g c d ( x , y , z …   ) = g c d ( x , y − x , z − y , …   ) gcd(x,y,z\dots)=gcd(x,y-x,z-y,\dots) gcd(x,y,z)=gcd(x,yx,zy,) 容易证明,当差值为负时取绝对值依然满足上式。设 d [ i ] = c o l [ i ] − c o l [ i − 1 ] d[i]=col[i]-col[i-1] d[i]=col[i]col[i1],则区间的最大公约数为
g c d ( ∑ i = 1 l d [ i ] , d [ l + 1 ] , … , d [ r ] ) gcd(\sum\limits_{i=1}^{l}d[i],d[l+1],\dots,d[r]) gcd(i=1ld[i],d[l+1],,d[r]) 那么线段树维护差分数组的区间和,区间绝对值的最大值与区间的最大公约数即可。

#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
#define sg_size (1 << 18) - 1
struct node
{
    int gcd, mx, sum;
} sg[sg_size];
int n, m, col[maxn];

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

inline void pushup(int k, int chl, int chr)
{
    sg[k].gcd = gcd(sg[chl].gcd, sg[chr].gcd);
    sg[k].mx = max(sg[chl].mx, sg[chr].mx);
    sg[k].sum = sg[chl].sum + sg[chr].sum;
}

void init(int k, int l, int r)
{
    if (r - l == 1)
    {
        int d = col[l] - col[l - 1];
        sg[k] = node{abs(d), abs(d), d};
    }
    else
    {
        int chl = (k << 1) + 1, chr = (k << 1) + 2, m = (l + r) >> 1;
        init(chl, l, m);
        init(chr, m, r);
        pushup(k, chl, chr);
    }
}

void update(int a, int b, int x, int k, int l, int r)
{
    if (a <= l && r <= b)
    {
        int d = sg[k].sum + x;
        sg[k] = node{abs(d), abs(d), d};
    }
    else if (l < b && a < r)
    {
        int chl = (k << 1) + 1, chr = (k << 1) + 2, m = (l + r) >> 1;
        update(a, b, x, chl, l, m);
        update(a, b, x, chr, m, r);
        pushup(k, chl, chr);
    }
}

node query(int a, int b, int k, int l, int r)
{
    if (b <= l || r <= a)
    {
        return node{0, 0, 0};
    }
    else if (a <= l && r <= b)
    {
        return sg[k];
    }
    int chl = (k << 1) + 1, chr = (k << 1) + 2, m = (l + r) >> 1;
    node c = query(a, b, chl, l, m);
    node d = query(a, b, chr, m, r);
    return node{gcd(c.gcd, d.gcd), max(c.mx, d.mx), c.sum + d.sum};
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", col + i);
    }
    int ub = n + 1;
    init(0, 1, ub);
    while (m--)
    {
        int op, l, r;
        scanf("%d%d%d", &op, &l, &r);
        if (op == 1)
        {
            int x;
            scanf("%d", &x);
            update(l, l + 1, x, 0, 1, ub);
            update(r + 1, r + 2, -x, 0, 1, ub);
        }
        else if (op == 2)
        {
            printf("%d\n", query(l + 1, r + 1, 0, 1, ub).mx);
        }
        else
        {
            int a = query(1, l + 1, 0, 1, ub).sum;
            int b = query(l + 1, r + 1, 0, 1, ub).gcd;
            printf("%d\n", gcd(a, b));
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值