2020/9/23 Acwing-差分

题目

输入一个长度为n的整数序列。
接下来输入m个操作,每个操作包含三个整数l, r, c,表示将序列中[l, r]之间的每个数加上c。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数序列。
接下来m行,每行包含三个整数l,r,c,表示一个操作。
输出格式
共一行,包含n个整数,表示最终序列。
数据范围
1≤n,m≤100000,
1≤l≤r≤n,
−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2

题解

问题分析

题目可以理解为:给出一个数组,然后要求在给定区间加上某个值,并且这个操作是多次的. 一般的思路是采用暴力解法,直接遍历数组到指定位置增加指定数值即可.然而每次进行这个操作实际都要进行一个O(n)复杂度的遍历数组.为了简化这个操作,才有了差分的解决办法,使得仅在求差分数组时是O(n),而每次对其内部的加值,时间复杂度都是O(1).

差分思路解析

差分数组和前缀和数组其实是可逆的一对数组,可以说知道两者任何一个数组,都可以直接推出另一个数组.差分数组就是用一个数组表示原数组相邻两数的差值.一般来说,数组的角标从1开始,因为我们希望原数组角标为0 的数组值也为0,这样差分数组第一项就是原数组第一项,第二项是原数组一二项的差以此类推.反过来知道差分数组,也很好求原数组:

//原数组(令为list[]) 差分数组(令为list_dif[])
list[i]=list_dif[i]+list[i-1];

如果希望将差分数组直接变为原数组就更简单了:

list_dif[i]+=list[i-1];

以上说明了从差分到原数组的变化关系,但是没有说明为什么要用差分,用差分到底有什么好处?
对原数组的一个区间的增减可以转换为对差分数组两个值的增减
由于差分数组表示原数组后一项与前一项的差值,这就导致了一旦差分数组某一个值增加了.会引起后面全部增加.对于这一点,可以理解为站队,每个人都只关注前面一个人的位置,但是如果有一个人位置站错,那么这个人后面的所有人都会"整整齐齐"的站错.
那么如果我们想让某一部分人站错,我们要做的就是让起始的人站错(差分数组增加),由于这会导致后面所有的都站错,而我们只希望一部分站错,因此在结尾处的后一个人要把这个错误修改掉(差分数组在此处减少).

代码表示为:

//list_dif数组里存储的就是原数组的差分数组
insert(int l,int r,int c,int[] list_dif){
     list_dif[l]+=c;
     list_dif[r+1]-=c;
}

因此整体思路是,给出原数组->求出差分数组->对差分数组做修改->由差分数组求出要求的原数组

差分数组的求法

现在我们已经知道了大体思路,只剩的问题就是差分数组要怎么求.
要记得,我们写的insert函数表示的是对原数组进行操作会转换为对差分数组(也就是关键是一定要有一个对应原数组的差分数组)的变化.
那么请问如果原数组是全0,他的差分数组是不是也是全0?这样不就是对应上了吗?
接下来要做的,就是在那个全0的原数组的某个指定区间->一个确定的位置i增加一个数a[i],a[i]依次插入,差分数组也随之不断修改,但是永远是对应原数组的,这样当原数组已经变为了a[i]时,他的差分数组同时也保存在了list_dif数组中.这样我们就得到了原数组的差分数组.

源代码

import java.util.Scanner;
import java.io.BufferedInputStream;
class Main{
    public static void main(String[] args){
        int N=100100;
        int[] list=new int[N];
        int[] list_dif=new int[N];
        Scanner sc=new Scanner(new BufferedInputStream(System.in));
        int n=sc.nextInt();
        int m=sc.nextInt();
        for(int i=1;i<=n;i++){
            list[i]=sc.nextInt();
        }
        for(int i=1;i<=n;i++) insert(i,i,list[i],list_dif);
        
        while(m-->0){
            int l=sc.nextInt();
            int r=sc.nextInt();
            int c=sc.nextInt();
            insert(l,r,c,list_dif);
        }
        
        for(int i=1;i<=n;i++) list_dif[i]+=list_dif[i-1];
        for(int i=1;i<=n;i++) System.out.print(list_dif[i]+" ");
    }
    static void insert(int l,int r,int c,int[] list_dif){
        list_dif[l] +=c;
        list_dif[r+1] -=c;
    }
}

优化的思考

Q:代码里由于给出了各个数的取值范围,因此直接new了两个最大值的数组,但是因为在之前输入的时候已经给定了数组的大小了.所以有没有节约内存空间的申请数组的方法?即根据输入的数值,申请对应长度的数组?
A:答案是可以的.但是一开始我的想法是:由于要输入n个数,而角标为0的数要使用初始值(即0),因此数组的大小为(n+1)即可,但是发现出现了角标越界异常.
问题原因及解决办法:这是因为在使用insert函数一步步建立原数组的差分数组的过程中,每次在原数组填一个数list[i]时,其实都是在差分数组修改了差分数组的list_dif[i],跟list_dif[i+1],也就是说在原数组最后一个数插入完成时,其实多占用了差分数组的一个值,因此,差分数组的长度就不能是n+1了,而应该是n+2.

  int[] list=new int[n+1];
  int[] list_dif=new int[n+2];
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值