小阳的贝壳-(差分+线段树+gcd)

链接:https://ac.nowcoder.com/acm/contest/949/H
来源:牛客网

题目描述

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

1 l r x:给 [l,r][l,r][l,r] 区间里所有贝壳的颜色值加上 xxx 。

2 l r:询问 [l,r][l,r][l,r] 区间里所有相邻贝壳 颜色值的差(取绝对值) 的最大值(若 l=rl = rl=r 输出 0)。

3 l r :询问 [l,r][l,r][l,r] 区间里所有贝壳颜色值的最大公约数。

输入描述:

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

输出描述:

共 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≤100000

1≤coli≤1000

1≤opt≤3,1≤l≤r≤n

第一次遇到差分的题,记录一下差分思想。

1.差分是什么?

差分就是后一个数减去前一个数。

例如3、9、4、6、6是原数组,差分数组就是3、6、-5、2、0后一个数减去前一个数

差分数组:(a1-a2)、(a3-a2)、(a4-a3)、(a5-a4)

2.有哪些性质?

  • 差分数组的某个前缀和=原数组对应的数。
  • 对于前缀和3、12、16、22、28的前缀差分数组就是3、9、4、6、6即原数组。
  • 原数组的一个区间[l,r]同时加上一个数x,区间内的差分数组不变,在l位置+x,在(r+1)位置-x。
  • 结合gcd,gcd(a,b)=gcd(a,a-b),gcd(b,c)=gcd(b,b-c),gcd(a,b,c)=gcd(a,a-b,b-c)。原理是辗转相减法。

解题思路:不会线段树的出门左转

对于操作2,不是常规的求区间最大值-最小值,而是相邻差值的最大值,提示用差分,差分就是相邻差值,求差分数组区间最大的相邻差值=常规的线段树区间求最值。对于1操作用差分的话就是修改两个值,加上维护操作3的区间gcd可以用线段树的单点修改的区间维护。还有一个点,区间gcd用差分求的话第一个数是原数组中的数,而不是差分,既然都用上差分和线段树了,区间求和也可以顺便维护,可以用线段树再维护一个区间求和用于求差分数组的前缀和得到原数组中的数。还有一个点,用gcd用差分会有负数和0,gcd函数加入判断。(这里我用了树状数组求前缀和)

import java.util.*;

public class Main {

    static int[] cf=new int[400005];//线段树数组,维护 区间差分最值 
    static int[] gcd=new int[400005];//线段树数组,维护 区间gcd
    static int[] a=new int[100005];//原数组
    static int[] c=new int[100005];//差分数组
    static int[] t=new int[100005];//维护差分数组的树状数组
    static int n,m;
    public static void main(String[] args) {
        Scanner scan=new Scanner(System.in);
        n=scan.nextInt();
        m=scan.nextInt();
        for(int i=1;i<=n;i++)
            a[i]=scan.nextInt();
        for(int i=1;i<=n;i++)
            c[i]=a[i]-a[i-1];//差分数组
        build(1, n, 1);
        while(m>0) {
            m--;
            int l,r,x;
            int id=scan.nextInt();
            l=scan.nextInt();
            r=scan.nextInt();
            
            if(id==1) {//区间[l,r]都加上x,只修改t[l]和t[r+1],用树状数组维护差分数组

                x=scan.nextInt();
                add(l,x);
                add(r+1,-x);//前面加后面减
                update(l, x, 1, n, 1);
                if(r<n)
                    update(r+1, -x, 1, n, 1);
            }
            else if(id==2) {
                if(l==r)
                    x=0;
                else 
                    x=query_max(l+1, r, 1, n, 1);
                System.out.println(x);
            }
            else {
                //需要先通过树状数组获取连续gcd的第一个值
                x=a[l]+get_sum(l);
                if(l!=r)
                    x=gcd(x, query_gcd(l+1, r, 1, n, 1));
                System.out.println(x);
            }
        }
    }
    
    public static void up(int rt) {
        cf[rt]=Math.max(Math.abs(cf[rt*2]), Math.abs(cf[rt*2+1]));
        gcd[rt]=gcd(gcd[rt*2], gcd[rt*2+1]);
    }
    
    public static void build(int l,int r,int rt) {
        if(l==r) {//遇到叶子节点
            cf[rt]=c[l];
            gcd[rt]=c[l];
            return;
        }
        int m=(l+r)/2;
        build(l, m, rt*2);
        build(m+1, r, rt*2+1);
        up(rt);
    }
    
    public static int gcd(int a,int b) {
        if(a==0 && b==0)
            return 0;
        if(a<0) //用差分求gcd可能会有负数
            a=-a;
        if(b<0)
            b=-b;
        if(b==0)
            return a;
        return gcd(b,a%b);
    }
    
    public static void update(int p,int x,int l,int r,int rt) {//点修改
        if(l==r) {//搜到叶子节点才修改 
            cf[rt]+=x;
            gcd[rt]+=x;
            return;
        }
        int m=(l+r)/2;
        if(p<=m)
            update(p, x, l, m, rt*2);
        else 
            update(p, x, m+1, r, rt*2+1);
        up(rt);
    }
    
    public static int query_max(int L,int R,int l,int r,int rt) {//查询区间中相邻差的最大值
        if( L<=l && r<=R ) {
            return Math.abs(cf[rt]);
        }
        int res=0;
        int m=(l+r)/2;
        if(L<=m)//左右查询找最大的差分
            res=Math.max(res, query_max(L, R, l, m, rt*2));
        if(R>m)
            res=Math.max(res, query_max(L, R, m+1, r, rt*2+1));
        return Math.abs(res);
    }
    
    public static int query_gcd(int L,int R,int l,int r,int rt) {//查询区间gcd
        if( L<=l && r<=R ) {
            return gcd[rt];
        }
        int res=0;//0和任何数的gcd都是那个数
        int m=(l+r)/2;
        if(L<=m)//左右查询找最大的差分
            res=gcd(res, query_gcd(L, R, l, m, rt*2));//kao,这里复制上面的max修改没改全,浪费我半天时间
        if(R>m)
            res=gcd(res, query_gcd(L, R, m+1, r, rt*2+1));
        return res;
    }
    
    public static int lowbit(int x) {
        return x&(-x);
    }
    
    public static void add(int x,int val) {//定点添加
        
        while(x<=n) {
            t[x]+=val;
            x=x+lowbit(x);
        }
    }
    public static int get_sum(int x) {//树状数组求和
        int res=0;
        while(x!=0) {
            res+=t[x];
            x=x-lowbit(x);
        }
        return res;
    }
    
}

猜你喜欢

转载自www.cnblogs.com/shoulinniao/p/12424146.html
今日推荐