小明的彩灯
- 题目描述
- 暴力解法
- 差分的思路和模板
- 差分解法
题目描述
小明拥有 N个彩灯,第 i个彩灯的初始亮度为 ai。
小明将进行 Q次操作,每次操作可选择一段区间,并使区间内彩灯的亮度 +x(x可能为负数)。
求 Q次操作后每个彩灯的亮度(若彩灯亮度为负数则输出 0)。
输入输出样例
输入
5 3
2 2 2 1 5
1 3 3
4 5 5
1 1 -100
输出
0 5 5 6 10
暴力解法
看见这个题其实第一反应是暴力应该挺好写,确实,思路很明确,就是将[ l , r ]上的数都 “+x”。呢就循环,时间复杂度也是O(n),甚至空间开辟的更少,有一点要注意,数据范围,int是不满足的。
#include<iostream>
using namespace std;
const int n=5*1e5;
long long A[n];
int main()
{
int N,Q;
scanf("%d%d",&N,&Q);
for(int i=1;i<=N;i++)
scanf("%lld",&A[i]);
while(Q--)
{
long long l,r,x;
scanf("%lld%lld%lld",&l,&r,&x);
for(int i=l;i<=r;i++)
A[i]+=x;
}
for(int i=1;i<=N;i++)
{
if(A[i]<0) A[i]=0;
printf("%lld ",A[i]);
}
return 0;
}
差分的思路和模板
差分差分顾名思义,就是把加起来的数,再拆开,研究过前缀和的便知道,差分便是前缀和的逆运算。
一维数组差分
就拿样例的数组 a [2,2,2,1,5],
则它的差分数组 b [2,0,0,-1,4]
假设我们让数组区间[2,4]元素都增加 2
则数组a[2,4,4,3,5]
差分数组b[2,2,0,-1,3]
我们可以发现规律,对于差分数组b,对于任意区间[l,r],“+x” 它的数组变化为b[l]+=x, b[r+1]-=x;
其次为了方便对数据进行差分,通常将数据存放再a[1]的位置,a[0]空为0
呢说到这了,差分要如何存储呢?
这里依然使用,a [2,2,2,1,5]作为模板,我们现在要做的是将其拆为原型
a[i-1]=b[1]+b[2]+b[3]+····+b[i-1]
a[i]=b[1]+b[2]+b[3]+·······+b[i-1]+b[i]
So, b[i]=a[i]-a[i-1]
这里可以开个小差,考考大家
一维前缀和是咋存的呢?
笨蛋,移个项,a[i]=a[i-1]+b[i],记住了吗?嘿嘿
之后很总要的一点,为什么可以利用差分?
上面也有说,对于差分数组b,对于任意区间[l,r],“+x” 它的数组变化为b[l]+=x, b[r+1]-=x,由于,差分又重组,前面加一个元素,其实相当于后面每个元素在求总和时都加了一个元素,超出区间的人家不需要加这个元素,你就再减去,这个适合,数很多的情况,不用一个一个加了
差分解法
#include <iostream>
using namespace std;
const int n=5*1e5;
long long A[n]={0},B[n]={0};
int main()
{
int N,Q;
scanf("%d%d",&N,&Q);
for(int i=1;i<=N;i++)
{
scanf("%lld",&A[i]);
B[i]=A[i]-A[i-1];//差分
// cout<<B[i];
}
while(Q--)
{
long long l,r,x;
scanf("%lld%lld%lld",&l,&r,&x);
B[l]+=x;
B[r+1]-=x;//
}
for(int i=1;i<=N;i++)
{
A[i]=A[i-1]+B[i];
}
for(int i=1;i<=N;i++)
{
if(A[i]<0) A[i]=0;
printf("%lld ",A[i]);
}
// 请在此输入您的代码
return 0;
}
其实,时间消耗差不多,甚至暴力的更好,这个只是差分模板很好的一个题,但是当数据很大很多,还是建议使用差分