牛客网 小阳的贝壳(差分数组+线段树)

题目传送门

小阳的贝壳

题目描述

小阳手中一共有 n 个贝壳,每个贝壳都有颜色,且初始第 i 个贝壳的颜色为 col_i 。现在小阳有 3 种操作:

1 l r x:给 [l,r] 区间里所有贝壳的颜色值加上 x 。
2 l r:询问 [l,r] 区间里所有相邻贝壳 颜色值的差(取绝对值) 的最大值(若 l=r 输出 0)。
3 l r :询问 [l,r] 区间里所有贝壳颜色值的最大公约数。

输入描述:

第一行输入两个正整数 n,m,分别表示贝壳个数和操作个数。
第二行输入 n 个数 colicol_icoli,表示每个贝壳的初始颜色。
第三到第 m+2 行,每行第一个数为 opt,表示操作编号。接下来的输入的变量与操作编号对应。

输出描述:

共 m 行,对于每个询问(操作 2 和操作 3)输出对应的结果。

输入
5 6
2 2 3 3 3
1 2 3 3
2 2 4
3 3 5
1 1 4 2
3 2 3
2 3 5
输出
3
3
1
3
思路

线段树维护差分数组,差分数组f[i]等于原数组相邻两个元素之差,f [i] = col[i]-col[i-1],设数组下标从1开始,那么f [1]=col [1],f [2]=col [2] - col [1]。

本题目用到的差分数组性质:

  1. 原数组的第 i 项为差分数组的前缀和,即 c o l [ i ] = f [ 1 ] + f [ 2 ] + f [ 3 ] + . . . + f [ i ] col[i]=f[1]+f[2]+f[3]+...+f[i] col[i]=f[1]+f[2]+f[3]+...+f[i]
  2. 原数组区间修改时,假设[l,r]要加上x,差分数组对应修改为两次单点修改: f [ l ] + x f[l]+x f[l]+x f [ r + 1 ] − x f[r+1]-x f[r+1]x ,这样求 c o l [ i ]   i ∈ [ l , r ] col[i]\space i∈[l,r] col[i] i[l,r]时,会保证加上f[l]加过的x,求之后的元素 c o l [ i ]   i ∈ ( r , + ∞ ) col[i]\space i∈(r,+\infty) col[i] i(r,+)时,会用f[r+1]减去的x与f[l]加过的x相抵消

其他性质:差分数组

本题解法

  1. 第一种操作,就使用线段树进行差分数组的单点修改 : f [l]+x 和 f [r+1]-x。

  2. 第二种操作,[ l , r ]相邻元素差的绝对值的最大值,则是差分数组 [ l+1 , r ] 的绝对值的区间最大值,可以用线段树维护绝对值的最大值

  3. 第三种操作,求 [ l , r ] 的gcd。同更相减损法的原理, g c d ( c o l [ i ] , c o l [ i + 1 ] ) = g c d ( c o l [ i ] , ∣ c o l [ i + 1 ] − c o l [ i ] ∣ ) gcd(col[i],col[i+1])=gcd(col[i],|col[i+1]-col[i]|) gcd(col[i],col[i+1])=gcd(col[i],col[i+1]col[i]),后者的 ∣ c o l [ i + 1 ] − c o l [ i ] ∣ |col[i+1]-col[i]| col[i+1]col[i]就是f[i+1]的绝对值,因此需要求解[l,r]的gcd,可以使用线段树维护差分数组 [ l+1 , r ] 的gcd,再与col [ l ]计 算一次gcd。注意特判 l==r 的情况,l 等于r时,l+1会大于r的!!

代码
#include <bits/stdc++.h>
using namespace std;
#define MAXN 100100
struct node{
    int l,r;
    int g,ma,sum;
}tree[MAXN<<2];
int num[MAXN];
int arr[MAXN];
int gcd(int a,int b){
    return !b ? a : gcd(b,a%b);
}
void pushup(int d){
    tree[d].sum=tree[d<<1].sum+tree[d<<1|1].sum;
    tree[d].ma=max(tree[d<<1].ma,tree[d<<1|1].ma);
    tree[d].g=gcd(tree[d<<1].g,tree[d<<1|1].g);
}
void build(int d,int l,int r){
    tree[d].l=l,tree[d].r=r;
    if (l==r){
        tree[d].sum=arr[l];
        tree[d].g=tree[d].ma=abs(arr[l]);
        return;
    }
    int mid=l+r>>1,lson=d<<1,rson=d<<1|1;
    build(lson,l,mid);
    build(rson,mid+1,r);
    pushup(d);
}
void update(int d,int pos,int val){
    if (tree[d].l==tree[d].r && tree[d].l==pos){
        tree[d].sum+=val;
        tree[d].ma=tree[d].g=abs(tree[d].sum);
        return;
    }
    int mid=tree[d].l+tree[d].r>>1,lson=d<<1,rson=d<<1|1;
    if (pos<=mid){
        update(lson,pos,val);
    }else {
        update(rson,pos,val);
    }
    pushup(d);
}
int query(int d,int l,int r,int tag){
    if (tree[d].l==l && tree[d].r==r){
        return tag==2 ?tree[d].ma:tree[d].g;
    }
    int mid=tree[d].l+tree[d].r>>1,lson=d<<1,rson=d<<1|1;
    if (r<=mid){
        return query(lson,l,r,tag);
    }else if (l>mid){
        return query(rson,l,r,tag);
    }else {
        return tag==2 ? max(query(lson,l,mid,tag),query(rson,mid+1,r,tag))
        : gcd(query(lson,l,mid,tag),query(rson,mid+1,r,tag));
    }
}
int sum_query(int d,int l,int r){
    if (tree[d].l==l && tree[d].r==r){
        return tree[d].sum;
    }
    int mid=tree[d].l+tree[d].r>>1,lson=d<<1,rson=d<<1|1;
    if (mid>=r){
        return sum_query(lson,l,r);
    }else if (mid<l){
        return sum_query(rson,l,r);
    }else {
        return sum_query(lson,l,mid)+sum_query(rson,mid+1,r);
    }
}
int main (){
    int n,m,opt,l,r,x;
    while (scanf("%d%d",&n,&m)!=EOF){
        for (int i=1;i<=n;i++){
            scanf("%d",&num[i]);
            arr[i]=num[i]-num[i-1];
        }
        build(1,1,n);
        for (int i=0;i<m;i++){
            scanf("%d",&opt);
            if (opt==1){
                scanf("%d%d%d",&l,&r,&x);
                update(1,l,x);
                update(1,r+1,-x);
            }else {
                scanf("%d%d",&l,&r);
                if (opt==2){
                    l==r?puts("0"):printf("%d\n",query(1,l+1,r,opt));
                }else {
                    l==r?printf("%d\n",sum_query(1,1,l)):printf("%d\n",gcd(sum_query(1,1,l),query(1,l+1,r,opt)));
                }
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值