P7402题解
lupengheyyds · · 题解
原题连接
背景
其他的题解对这道题的做法已经有了大概的讲解,但是对于正解的思路与细节处理说得云里雾里,所以说我将对正解的思路及具体细节作诠释,不再赘述部分分数的解法。
分析
初步转化
看见区间加减,便不由得想到差分,于是建立一个差分数组
接着分析,一个区间真正能作出贡献的只有最大值与最小值,也就是说,当我们已经得到一段的极大值与极小值的时候,便应该直接划分,而非继续将其他的数纳入这个区间,最终使得整个划分出来的区间单调不下降或单调不上升。转化为数学语言就是:对于所有划分的区间
根据前面的分析,现在就需要我们单点修改,区间查询。那不就是线段树吗?于是我们可以尝试从线段树的角度考虑如何合并两个区间。
线段树分析
假设有这样一个区间(下面表示原数组,上面表示差分数组):
如果在差分线段树上的话,就会被分成这样两个区间:
那么对应反映在原数组上就是这样:
如果两个差分区间符合上述的“初步转换”中的条件的话就可以直接累加,合并成一个区间(过于简单,没有画出)。
但如果两个差分区间不符合条件也就是需要必须分开的时候,就会有一下两种情况:
- 分给左边
- 分给右边
这是我们就会发现,对于差分区间,其左右两边的值可能选或不选,那么在处理线段树的时候,我们就应该对应存上左右两端选或不选的情况。
最后再输出整个区间,由于整个区间的左右两端选了的答案一定比不选优(多考虑了两个值),所以直接输出两端都选的情况就行,根本不用作比较。
对了,记得开
如果还没有理解,就可以看代码。
代码
#include<bits/stdc++.h>
#define int long long//记得开long long
using namespace std;
const int szl=8e5+5;
int sgt[szl][2][2];//第一维是线段树节点编号,第二位表示左端点状态,第三位表示由端点状态,0表示不选,1表示选
int a[szl],d[szl],root=1;
void Updata(int p,int mid){
for(int i=0;i<=1;i++)
for(int j=0;j<=1;j++)
if(d[mid]*d[mid+1]>=0)sgt[p][i][j]=sgt[p<<1][i][1]+sgt[p<<1|1][1][j];//可以合并成一个区间
else sgt[p][i][j]=max(sgt[p<<1][i][0]+sgt[p<<1|1][1][j],sgt[p<<1][i][1]+sgt[p<<1|1][0][j]); //不能合并成一个区间
return;
}
void Add(int p,int l,int r,int pos){
if(l==r){
sgt[p][1][1]=abs(d[l]);
return;
}
int mid=l+r>>1;
if(pos<=mid)Add(p<<1,l,mid,pos);
else Add(p<<1|1,mid+1,r,pos);
Updata(p,mid);
return;
}
signed main(){
int n,q;cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>a[i];
d[i]=a[i]-a[i-1];
if(i==1)continue;
Add(root,2,n,i);
//也可以写在外面O(N)建树,但这样写慢却简单,少些一个函数,反正不会超时,减少错误率
//注意:差分数组从2到n的区间才有效 。
}
while(q--){
int l,r,x;cin>>l>>r>>x;
if(l>1){
d[l]+=x;
Add(root,2,n,l);
}
if(r<n){
d[r+1]-=x;
Add(root,2,n,r+1);
}
cout<<sgt[root][1][1]<<endl;//最后直接输出,不用比较左右端点不选的情况
}
return 0;
}