差分数组浅析

什么是差分数组?

简单的来说,你有一个原始数组,而差分数组就是你原始数组的每一个数减上一个数
假设有原始数组a[10],差分数组b[10]
那么就有

for(int i=1;i<10;i++){
	b[i]=a[i]-a[i-1];
}

因为我们是从b[1]开始,所以要设个a[0]=0,而且a中的值是从a[1]开始存储

差分数组有什么用?

当我们要对一个区间进行修改,那么差分数组的作用一定就是求多次进行区间修改后的数组

为什么要用差分数组?

举个栗子

在这里插入图片描述
我们不难发现当一个区间同时加减一个数时,差分数组只会改变两个位置的值,如图片的b[1]和b[5],换言之,如果我们可以只修改差分数组,可以大大的减少代码的运算量

差分数组怎么运用到实际代码中?

我们前面知道了差分数组的公式

b[i]=a[i]-a[i-1];

那么我们调转一下位置有:

a[i]=b[i]+a[i-1];

那么我们就可以通过不断维护差分数组最后得到我们想要的原始数组
(维护方法得具体看题目,不同题目的维护条件不同)

我推荐几个差分数组的题目,难度以此递增

杭电oj-1556-color the ball

这题有手就行
保姆快来喂饭

ac代码

#include<stdio.h>
#include<string.h>
using namespace std;
#define N 100005
int n,m,a[N],b[N],x1,x2;
int main(){
	while(~scanf("%d",&n)){
		if(n==0){
			break;
		}
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		for(int i=0;i<n;i++){
			scanf("%d %d",&x1,&x2);
			b[x1]++;
			b[x2+1]--;
		}
		for(int i=1;i<n;i++){
			a[i]=a[i-1]+b[i];
			printf("%d ",a[i]);
		}
		a[n]=a[n-1]+b[n];
		printf("%d\n",a[n]);
	}
	return 0;
}

北大oj-3263-Tallest Cow

这个题属于稍微要想一下的,一开始把所有原始数组都当作最高的,每次维护一次,都是中间的在-1,两边的端点奶牛高度没有变化

我一开始内存超时了两次(菜鸡)原因是用了memset,这里给自己打个提醒,memset能不用尽量不用

还有一个超超超级重要的小细节,这个题目你要去重,它不像上题的刷气球,这个题只要求能看到就行,不用对同一个地方更新两次

ac代码

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
const int N=1e4+10;
int a[N],b[N],vis[N][N];

int main(){
	int x1,x2,h,n,m;
	scanf("%d %d %d %d",&n,&x1,&h,&m);
	for(int i=0;i<=n+1;i++){
		a[i]=h;
	}
	for(int i=0;i<m;i++){
		scanf("%d %d",&x1,&x2);
		if(x1>x2){
			swap(x1,x2);
		}
		if(vis[x1][x2]){
			continue;
		}
		vis[x1][x2]=1;
		b[x1+1]--;
		b[x2]++;
	}
	for(int i=1;i<=n;i++){
		a[i]=a[i-1]+b[i];
		printf("%d\n",a[i]);
	}
	return 0;
}

P1438 无聊的数列

这是今天写到的一个巨经典的差分题,心疼不会线段树的自己

我终于知道差分数组的差分数组怎么用了qaq

上图!

假设原数组为a [ n ] ,差分数组为d [ n ] , d[ n ]的差分数组为e[ n ] ,你先别急它有什么用,我先给你看几个数学公式

d[ n ] = a[ n ] - a[ n - 1 ]
那么a [ n ] =d [ 1 ] +d [ 2 ] + … +d[ n ]

根据题目,当我们对 l 到 r 进行一个等差数列的加法时(首项为a1,公差为D) ,我们会发现

d [ l ] +=a1
d[ l + 1 ] 到 d [ r ] + = D
d [ r + 1 ] + = ( ( l - r) * D - a1 )
//因为a[ r ] += ( ( r - l ) * D + a1 )

你看到这会发现中间 l + 1 到 r 都加了 D ,是不是很眼熟?没错,这里你一看到肯定就会下意识的想到差分,所以差分的差分出现了(记得一开始说的差分的作用吗?差分是为了减少重复的运算量,所以才用差分)

我们继续对上面的公式进行推导

a [ n ] =d [ 1 ] +d [ 2 ] + … +d [ n ]
即 a [ n ] = e [ 1 ] * n + e [ 2 ] *( n - 1 )+…e [ n ] * 1
进行一下转化有
a [ n ] = e [ 1 ] * ( n + 1 ) + e [ 2 ] *( n + 1 )+…e [ n ] * ( n + 1 ) - ( e [ 1 ] *1 + e [ 2 ] * 2 +…e [ n ] * n)
即 a [ n ] = ( e [ 1 ] + e [ 2 ] + … e [ n ] ) * ( n + 1 ) - ( e [ 1 ] *1 + e [ 2 ] * 2 +…e [ n ] * n)

这个时候我们就能看到很明显的规律的,我们只用在e [ n ] 这个数组上进行改动就可以完整的得到我们想要的答案,对了,后面减的也看以看作是一个数列,可以更加有逻辑 看起来更好看

f [ n ] = e [ n ] * n

即总结了这些规律

在操作 1 中
e[l] 增加 a1
e[l + 1]增加 D- a1
e[r + 1] 增加 ( l - r - 1) * D - a1
e[r + 2] 增加 k - (l - r) * D
同时对 f 也需要更新,若 e [ i ] 增加了 k,那么 f [ i ] 增加 k * i,例如 e[l] 增加 k 则f [ l ] 增加 k * l

这些操作放到树状数组中同时对ef进行更新就行,贴上代码好理解一些

#include<cstdio>
#include<iostream>
#include<malloc.h>
using namespace std;
int *a,*f_bit,*e_bit;
int n,m;
int lowbit(int x){
	return x&(-x);
}
void update(int *bit,int x,int k){
	for(;x<=n;x+=lowbit(x)){
		bit[x]+=k;
	}
}
void updateef(int x,int k){
	update(e_bit,x,k);
	update(f_bit,x,k*x);
}
int sum(int *bit,int x){
	int ans=0;
	for(;x>0;x-=lowbit(x)){
		ans+=bit[x];
	}
	return ans;
}
int main(){
	int x,l,r,a1,D;
	cin>>n>>m;
	a=(int *)malloc((n+1)*sizeof(int));
	f_bit=(int *)malloc((n+1)*sizeof(int));
	e_bit=(int *)malloc((n+1)*sizeof(int));
	for(int i=1;i<=n;i++){
		scanf("%d",a+i);
		e_bit[i]=f_bit[i]=0;
	}
	//e1 = d1 = a1
	//f1 = e1 * 1
	updateef(1,a[1]);
	//e2 = d2 - d1 = (a2 - a1) - a1 = a2 - 2a1
	//f2 = e2 * 2
	updateef(2,a[2]-2*a[1]);
	for(int i=3;i<=n;i++){
		//di = ai - a(i - 1)
		//ei = di - d(i - 1) = ai - 2a(i - 1) + a(i - 2)
		//fi = ei * i
		int e=a[i]-2*a[i-1]+a[i-2];
		updateef(i,e);
	}
	for(int i=0;i<m;i++){
		cin>>x;
		if(x==1){
			scanf("%d %d %d %d",&l,&r,&a1,&D);
			updateef(l,a1);
			updateef(l+1,D-a1);
			updateef(r+1,(l-r-1)*D-a1);
			updateef(r+2,a1+(r-l)*D);
		}else{
			scanf("%d",&x);
			printf("%d\n",sum(e_bit,x)*(x+1)-sum(f_bit,x));
		}
	}
	free(e_bit);
	free(f_bit);
	return 0;
}

后面如果还有什么差分的题目,会陆续更新

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值