P4314 题解

题意简述

给出长度为 $n(1\le n\le10^5)$ 的序列 $a_i$,再进行 $q(1\le q\le10^5)$ 次操作,每次操作是对给出的 $l,r$ 进行以下四种之一:

  • 求 $a_l\sim a_r$ 的最大值。
  • 求 $a_l\sim a_r$ 的历史最大值。
  • 将 $a_l\sim a_r$ 全部加上给出的 $k$。
  • 将 $a_l\sim a_r$ 全部改成给出的 $k$。

题目分析

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

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

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

代码实现

#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 @ 2023-08-15 17:33  Hadtsti  阅读(1)  评论(0编辑  收藏  举报  来源