P4314 题解

题意简述

给出长度为 n(1n105) 的序列 ai,再进行 q(1q105) 次操作,每次操作是对给出的 l,r 进行以下四种之一:

  • alar 的最大值。
  • alar 的历史最大值。
  • alar 全部加上给出的 k
  • alar 全部改成给出的 k

题目分析

这个题和吉司机线段树在同一篇论文里面,感觉挺好玩就一起看了看。

看到这个题想必很多人都想直接设置两个懒标记 tag1tag2 表示每个结点维护区间加和区间赋值的情况,并在维护区间最大值的同时不断更新历史最大值,但这样有一个很明显的问题:一个结点在未下传标记时,其子结点的最大值没有及时算出来,所以其子结点的历史最大值也无法及时更新。

对于这一个问题,我们仍然可以采用懒标记解决。我们把着手点放在刚刚思路难以解决的,在相邻两次下传标记之间所更新的子结点的历史最大值的维护。不妨维护两次下传标记之间 tag1tag2 的最大值 maxtag1maxtag2,这样下一次下传标记时,由于两次下传标记之间子结点维护的最大值 Max 没有被更新(必须等到下传之后才更新),仍然是上一次下传标记所更新的值。那么所产生的可能的历史最大值只会是区间加得到的 Max+maxtag1 或者区间赋值(如果没有区间赋值操作就没有这种情况)得到的 maxtag2。每次下传标记的同时,就如刚刚所说利用维护的信息更新一下历史最大值,清空标记后重新计算 maxtag1maxtag2 即可。具体细节看代码理解。

代码实现

#include<bits/stdc++.h>
using namespace std;
int n,q,a[100010],l,r,x;
char op;
struct node
{
int l,r;
int mx,mxx;
int tag1,tag2,mxtag1,mxtag2;
bool flag;//表示有无区间赋值操作
}tr[400010];
void rd(int &x)
{
int f=1;
x=0;
char c=getchar();
for(;c>'9'||c<'0';c=getchar())
if(c=='-')
f=-1;
for(;c<='9'&&c>='0';c=getchar())
x=(x<<3)+(x<<1)+c-'0';
x*=f;
}
void rd(char &c)
{
c=getchar();
for(;c!='Q'&&c!='A'&&c!='P'&&c!='C';c=getchar());
}
void wt(int x)
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>=10)
wt(x/10);
putchar(x%10+'0');
}
void pushup(int p)
{
tr[p].mx=max(tr[p<<1].mx,tr[p<<1|1].mx);
tr[p].mxx=max(tr[p<<1].mxx,tr[p<<1|1].mxx);
}//子结点更新父节点
void addtag1(int p,int tag,int mxtag)//区间加打标记
{
tr[p].mxx=max(tr[p].mxx,tr[p].mx+mxtag);
tr[p].mx+=tag;
if(tr[p].flag)//如果有区间赋值就把这次加的值加到 tag2 上
{
tr[p].mxtag2=max(tr[p].mxtag2,tr[p].tag2+mxtag);//更新 tag2 最大值
tr[p].tag2+=tag;//一定要先更新最大值再更新 tag2!!我因此调了好久……
}
else
{
tr[p].mxtag1=max(tr[p].mxtag1,tr[p].tag1+mxtag);//没有区间赋值就直接加到 tag1 上。
tr[p].tag1+=tag;
}
}
void addtag2(int p,int tag,int mxtag)//区间赋值打标记
{
tr[p].tag2=tr[p].mx=tag;//直接赋值更新
tr[p].mxx=max(tr[p].mxx,mxtag);//更新历史最大值
if(tr[p].flag)
tr[p].mxtag2=max(tr[p].mxtag2,mxtag); //更新最大 tag2
else
{
tr[p].flag=1;
tr[p].mxtag2=mxtag; //最开始的最大标记直接赋值为打标记的值
}
}
void pushdown(int p)
{
addtag1(p<<1,tr[p].tag1,tr[p].mxtag1);//先下传更新区间加(区间赋值优先级更高,区间加完了之后也会被全部赋值)
addtag1(p<<1|1,tr[p].tag1,tr[p].mxtag1);
tr[p].tag1=tr[p].mxtag1=0;//清空标记
if(tr[p].flag)
{
addtag2(p<<1,tr[p].tag2,tr[p].mxtag2);//后下传更新区间赋值
addtag2(p<<1|1,tr[p].tag2,tr[p].mxtag2);
tr[p].flag=0;
tr[p].tag2=tr[p].mxtag2=0;//清空标记
}
}//下传标记
void build(int p,int l,int r)//建树
{
tr[p].l=l,tr[p].r=r;
if(l==r)
{
tr[p].mx=tr[p].mxx=a[l];//最大值和历史最大值初始都是 a_l
return;
}
int mid=l+r>>1;
build(p<<1,l,mid);//左子结点
build(p<<1|1,mid+1,r);//右子结点
pushup(p);
}
void change1(int p,int l,int r,int x)//区间加
{
if(tr[p].l>=l&&tr[p].r<=r)
{
addtag1(p,x,x);//修改的话历史最大值也取更改的值就好了
return;
}
pushdown(p);
int mid=tr[p].l+tr[p].r>>1;
if(mid>=l)
change1(p<<1,l,r,x);
if(mid<r)
change1(p<<1|1,l,r,x);
pushup(p);
}
void change2(int p,int l,int r,int x)//区间赋值
{
if(tr[p].l>=l&&tr[p].r<=r)
{
addtag2(p,x,x);//同上
return;
}
pushdown(p);
int mid=tr[p].l+tr[p].r>>1;
if(mid>=l)
change2(p<<1,l,r,x);
if(mid<r)
change2(p<<1|1,l,r,x);
pushup(p);
}
int query1(int p,int l,int r)//查询最大值
{
if(tr[p].l>=l&&tr[p].r<=r)
return tr[p].mx;
pushdown(p);
int mid=tr[p].l+tr[p].r>>1,res=INT_MIN;
if(mid>=l)
res=query1(p<<1,l,r);
if(mid<r)
res=max(res,query1(p<<1|1,l,r));
return res;
}
int query2(int p,int l,int r)//查询历史最大值
{
if(tr[p].l>=l&&tr[p].r<=r)
return tr[p].mxx;
pushdown(p);
int mid=tr[p].l+tr[p].r>>1,res=INT_MIN;
if(mid>=l)
res=query2(p<<1,l,r);
if(mid<r)
res=max(res,query2(p<<1|1,l,r));
return res;
}
int main()
{
rd(n);
for(int i=1;i<=n;i++)
rd(a[i]);
build(1,1,n);
rd(q);
while(q--)
{
rd(op),rd(l),rd(r);
switch(op)
{
case 'Q':
wt(query1(1,l,r)),putchar('\n');
break;
case 'A':
wt(query2(1,l,r)),putchar('\n');
break;
case 'P':
rd(x);
change1(1,l,r,x);
break;
case 'C':
rd(x);
change2(1,l,r,x);
}
}
return 0;
}
posted @   Hadtsti  阅读(4)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示