H-小阳的贝壳(差分数组+线段数||树状数组)

题目描述
小阳手中一共有 n 个贝壳,每个贝壳都有颜色,且初始第 i 个贝壳的颜色为 coli。现在小阳有 3 种操作:
1 l r x:给 [l,r] 区间里所有贝壳的颜色值加上 x 。
2 l r:询问 [l,r] 区间里所有相邻贝壳 颜色值的差(取绝对值) 的最大值(若 l=r 输出 0)。
3 l r :询问 [l,r]区间里所有贝壳颜色值的最大公约数。

输入描述:

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

输出描述:

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

示例1
输入

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

备注:
1≤n,m≤105,1≤coli,x≤103,1≤opt≤3,1≤l≤r≤n

思路:

首先介绍差分数组:
对于数组a: a1、a2、a3、a4、a5、、、an;
我们建立一个数组f,f是a的相邻两项的差,
f0=0;
f1=a1;
f2=a2-a1;
f3=a3-a2;
f4=a4-a3;
f5=a5-a4;

fn=an-a(n-1);

那么an=f的前n项和,在a的一个区间[l,r]加x时,只需要修改f[l]和f[r+1],求ai时通过f的前缀和来求解,使用树状数组或者线段数来维护f的区间和,就能将时间复杂度控制在O(logn)。

对于本题我们建立数组f来记录相邻coli的差值,用四个线段数(也可以用树状数组,后面有讲解)分别维护f的区间和、区间最大值、区间最小值、区间gcd。

对操作1,更新f[l]和f[r+1];
对操作2,查询f区间的最小值和最大值,两个取绝对值后最大的即是所求;
对操作3,求gcd(a[l],k);a[l]通过f的前缀和求出,k=f的[l+1,r]区间gcd;

对操作3的证明:
gcd(a,b)=gcd(a,b-a);
gcd(a,b,c)=gcd(a,b-a,c-b);

线段数实现参考代码:

#include <iostream>
#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<map>
#include<vector>
#include<set>
 
using namespace std;
 
typedef long long ll;
typedef pair<int,int> P;
 
const int INF=0x3f3f3f3f;
const int NINF=0xc0c0c0c0;
const int MAX_N=1e5+5;
const int MAX_M=500+5;
 
int n,q;
ll f[MAX_N+1];
ll dat0[(1<<17)*2+1],dat1[(1<<17)*2+1],dat2[(1<<17)*2+1],dat3[(1<<17)*2+1];
 
ll gcd(ll a,ll b){
    if(b==0)return a;
    return gcd(b,a%b);
}
 
void init(int k,int l,int r){
    if(l==r){
        dat0[k]=dat1[k]=dat2[k]=dat3[k]=f[l];
        return;
    }
    init(k*2,l,(l+r)/2);
    init(k*2+1,(l+r)/2+1,r);
    dat0[k]=dat0[k*2]+dat0[k*2+1];
    dat1[k]=min(dat1[k*2],dat1[k*2+1]);
    dat2[k]=max(dat2[k*2],dat2[k*2+1]);
    dat3[k]=gcd(dat3[k*2],dat3[k*2+1]);
}
 
void update(int i,int k,int l,int r){
    if(l==r&&l==i){
        dat0[k]=dat1[k]=dat2[k]=dat3[k]=f[l];
    }
    else if(i>=l&&i<=r){
        update(i,k*2,l,(l+r)/2);
        update(i,k*2+1,(l+r)/2+1,r);
        dat0[k]=dat0[k*2]+dat0[k*2+1];
        dat1[k]=min(dat1[k*2],dat1[k*2+1]);
        dat2[k]=max(dat2[k*2],dat2[k*2+1]);
        dat3[k]=gcd(dat3[k*2],dat3[k*2+1]);
    }
}
 
ll res0,res1,res2,res3;
void query(int a,int b,int k,int l,int r){
    if(a<=l&&b>=r){
        res0+=dat0[k];
        res1=min(res1,dat1[k]);
        res2=max(res2,dat2[k]);
        res3=gcd(res3,dat3[k]);
    }
    else if(b<l||a>r)return ;
    else {
        query(a,b,k*2,l,(l+r)/2);
        query(a,b,k*2+1,(l+r)/2+1,r);
    }
}
 
 
void get(int l,int r){
    res0=0;res1=INF;res2=NINF;res3=f[r];
}
 
int main(){
    scanf("%d%d",&n,&q);
    int pre=0;
    for(int i=1;i<=n;i++){
        int x;scanf("%d",&x);
        f[i]=x-pre;
        pre=x;
    }
    init(1,1,n);
    int t,l,r;
    ll x;
    while(q--){
        scanf("%d%d%d",&t,&l,&r);
        if(t==1){
            scanf("%lld",&x);
            f[l]+=x;
            update(l,1,1,n);
            f[r+1]-=x;
            update(r+1,1,1,n);
        }
        else if(t==2){
            if(l==r)printf("%d\n",0);
            else{
                get(l,r);
                query(l+1,r,1,1,n);
                printf("%lld\n",max(abs(res1),abs(res2)));
            }
        }
        else{
            get(1,l);
            query(1,l,1,1,n);
            ll k=res0;
            if(l==r)printf("%lld\n",k);
            else {
                get(l+1,r);
                query(l+1,r,1,1,n);
                printf("%lld\n",abs(gcd(k,res3)));
            }
        }
    }
    return 0;
}

树状数组实现参考代码:

#include <iostream>
#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<map>
#include<vector>
#include<set>
 
using namespace std;
 
typedef long long ll;
typedef pair<int,int> P;
 
const int INF=0x3f3f3f3f;
const int NINF=0xc0c0c0c0;
const int MAX_N=1e5+5;
const int MAX_M=500+5;
 
int n,q;
ll f[MAX_N+1];
ll bit0[MAX_N+1],bit1[MAX_N+1],bit2[MAX_N+1],bit3[MAX_N+1];
 
ll gcd(ll a,ll b){
    if(b==0)return a;
    return gcd(b,a%b);
}
 
ll sum(int i){
    ll s=0;
    while(i>0){
        s+=bit0[i];
        i-=i&-i;
    }
    return s;
}
 
void add(int i,ll x){
    while(i<=n){
        bit0[i]+=x;
        i+=i&-i;
    }
}
 
void update(int i){
    while(i<=n){
        bit1[i]=f[i];
        bit2[i]=f[i];
        bit3[i]=f[i];
        for(int j=1;j<(i&-i);j<<=1){
            bit1[i]=min(bit1[i],bit1[i-j]);
            bit2[i]=max(bit2[i],bit2[i-j]);
            bit3[i]=gcd(bit3[i],bit3[i-j]);
        }
        i+=i&-i;
    }
}
 
vector<ll> query(int l,int r){
    vector<ll> res(4);
    res[1]=INF;
    res[2]=NINF;
    res[3]=f[r];
    while(r>=l){
        res[1]=min(res[1],f[r]);
        res[2]=max(res[2],f[r]);
        res[3]=gcd(res[3],f[r]);
        r--;
        while(r-(r&-r)>=l){
            res[1]=min(res[1],bit1[r]);
            res[2]=max(res[2],bit2[r]);
            res[3]=gcd(res[3],bit3[r]);
            r-=r&-r;
        }
    }
    return res;
}
 
int main(){
    scanf("%d%d",&n,&q);
    int pre=0;
    for(int i=1;i<=n;i++){
        int x;
        scanf("%d",&x);
        f[i]=x-pre;
        pre=x;
    }
    for(int i=1;i<=n;i++){
        add(i,f[i]);
        update(i);
    }
    int t,l,r;
    ll x;
    while(q--){
        scanf("%d%d%d",&t,&l,&r);
        if(t==1){
            scanf("%lld",&x);
            add(l,x);
            add(r+1,-x);
            f[l]+=x;
            update(l);
            f[r+1]-=x;
            update(r+1);
        }
        else if(t==2){
            if(l==r)printf("%lld\n",0);
            else{
                vector<ll> res=query(l+1,r);
                printf("%lld\n",max(abs(res[1]),abs(res[2])));
            }
        }
        else {
            ll k=sum(l);
            if(l==r)printf("%lld\n",k);
            else printf("%lld\n",abs(gcd(k,query(l+1,r)[3])));
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值