差分数组的原理以及一种题型

19 篇文章 1 订阅

本文参考博客:https://blog.csdn.net/qq_31601743/article/details/105352885
问题背景
如果给你一个包含5000万个元素的数组,然后会有频繁区间修改操作,那什么是频繁的区间修改操作呢?比如让第1个数到第1000万个数每个数都加上1,而且这种操作时频繁的。

此时你应该怎么做?很容易想到的是,从第1个数开始遍历,一直遍历到第1000万个数,然后每个数都加上1,如果这种操作很频繁的话,那这种暴力的方法在一些实时的系统中可能就拉跨了。

因此,今天的主角就出现了——差分数组。

算法原型
比如我们现在有一个数组arr,arr={0,2,5,4,9,7,10,0}
在这里插入图片描述

那么差分数组是什么呢?其实差分数组本质上也是一个数组,我们暂且定义差分数组为d,差分数组d的大小和原来arr数组大小一样,而且di=arr[i]-arr[i-1],且d[i]=0,它的含义是什么?就是原来数组i位置上的元素和i-1位置上的元素作差,得到的值就是d[i]的值。

所以,例子中的arr数组其对应的差分数组值如下图所示。

在这里插入图片描述

那么构造了这么个玩意有什么用呢?难道是来浪费宝贵的内存空间的?嗯,确实是来浪费宝贵的内存了,但是却换了时间上的高效。

现在我们有这么一个区间修改操作,即在区间1~4上,所有的数值都加上3.
在这里插入图片描述

我们不要傻傻地遍历arr数组的[1,4]范围,然后再分别给每个值加上3,我们此时更改差分数组d即可。

显而易见,差分数组d在[2,4]范围内的值都不用改变,只需要改变差分数组位置1和位置5的值即可,即d[1]=d[1]+3,而d[5]=d[5]-3,其余不变,为什么呢?因为差分数组的定义——d[i]=arr[i]-arr[i-1]

在这里插入图片描述

当更新完差分数组d的值后,原始arr的值可以用差分数组d求得
d[i]=d[i]+d[i-1] //需要更新d[i]的值
arr[i] = d[i]+d[i-1]
即arr[i]=d[i]+d[i-1]+d[i-2]+…+d[0];因为上面的差分数组的值是由arr相邻相减获得(d[i] = arr[i]-arr[i-1])

总结
可以看到,如果需要对[L,R]范围内所有数都进行相同的操作,我们不需要从[L,R]遍历然后在每个值上进行相同操作,只需要在差分数组d中改变L和R+1的值即可。但是在查询arr数组中某个位置的数时,却要根据差分数组从前往后递推求值。

所以,该方法适用于区间频繁修改,而且这个区间范围是比较大的,离线查询的情况。

利用差分数组解题:
题目链接:https://www.dotcpp.com/oj/problem1501.html


```c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	int N, M;
	int Li, Ri, Ci;
	scanf("%d %d", &N, &M);
	int* number = (int*)malloc(sizeof(int) * (N+2));
	int* cut_array = (int*)malloc(sizeof(int) * (N+2)); //差分数组
	memset(number, 0, sizeof(int) * (N+2));
	memset(cut_array, 0, sizeof(int) * (N+2));
	for (int i = 0; i < M; i++)
	{
		scanf("%d %d %d", &Li, &Ri, &Ci);
		cut_array[Li] += Ci; //更新差分数组的值
		cut_array[Ri+1] -= Ci;
	}

	for (int i = 1; i <= N; i++)
	{
		//int temp = cut_array[i] + cut_array[i - 1];
		//number[i] = temp;
		//cut_array[i] = temp;

		cut_array[i] = cut_array[i] + cut_array[i - 1];
		number[i] = cut_array[i];
		printf("%d ", number[i]);
	}
	printf("\n");
	free(number);
	free(cut_array);
	return 0;
}

本题可以节省一半的空间:只需要记录差分数组的值即可,(因为差分数组d和原始数组arr初始化都为0)

#include<iostream>
using namespace std;
int d[100005]; //d[i]表示第i个小朋友比第i-1个小朋友多的苹果
int main()
{
    int n,m,l,r,c;
    cin>>n>>m;
    while(m--){
        cin>>l>>r>>c;
        d[l]+=c;    //每一次发苹果第l个小朋友比第l-1个小朋友多c个,
        d[r+1]-=c;  //第r+1个小朋友比第r个小朋友少c个。
}
    for(int i=1;i<=n;i++){
        d[i]+=d[i-1];   //根据差分数组的性质进行求和
        cout<<d[i]<<" ";
    }
    cout<<endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值