BZOJ 1858 [Scoi2010]序列操作 - 线段树

大概考的就是一个tag标记的先后顺序和相互影响的问题

这道题涉及到两个tag,一个是set,一个是翻转rev,假设先rev,set后入,set直接覆盖掉rev即可;假设是先set再rev,rev直接修改set标记。
pushdown的顺序:
因为有set函数保证之前的rev已清空,如果有rev标记则一定是后来的rev,所以先进行set,然而这里的set还应该覆盖掉子节点的rev,否则子节点的rev保留会被认为是set之后的rev,答案会出错

其次合并区间前后缀,注意同时维护0,1的相关值,rev时直接交换即可。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>

using namespace std;

const int maxn=100005;

struct tree
{
    int sum,pre0,pre1,sub0,sub1,set,len0,len1,rev;
}t[maxn<<2];

void maintain(int ro,int l,int r)
{
    t[ro].sum=t[ro<<1].sum+t[ro<<1|1].sum;
    int mid=l+r>>1;

    t[ro].sub1=t[ro<<1|1].sub1;
    if(t[ro<<1|1].sub1==r-mid)t[ro].sub1+=t[ro<<1].sub1;
    t[ro].pre1=t[ro<<1].pre1;
    if(t[ro<<1].pre1==mid-l+1)t[ro].pre1+=t[ro<<1|1].pre1;

    t[ro].sub0=t[ro<<1|1].sub0;
    if(t[ro<<1|1].sub0==r-mid)t[ro].sub0+=t[ro<<1].sub0;
    t[ro].pre0=t[ro<<1].pre0;
    if(t[ro<<1].pre0==mid-l+1)t[ro].pre0+=t[ro<<1|1].pre0;

    t[ro].len1=max(t[ro<<1].sub1+t[ro<<1|1].pre1,max(t[ro<<1].len1,t[ro<<1|1].len1));
    t[ro].len0=max(t[ro<<1].sub0+t[ro<<1|1].pre0,max(t[ro<<1].len0,t[ro<<1|1].len0));
}
void rever(int ro)
{
    swap(t[ro].len0,t[ro].len1);
    swap(t[ro].sub0,t[ro].sub1);
    swap(t[ro].pre0,t[ro].pre1);
}
void pushdown(int ro,int l,int r)
{
    int mid=l+r>>1;
    if(t[ro].set==0)
    {
        t[ro<<1].set=t[ro<<1|1].set=t[ro].set;
        t[ro<<1].sub0=t[ro<<1].pre0=mid-l+1;
        t[ro<<1|1].sub0=t[ro<<1|1].pre0=r-mid;
        t[ro<<1].sub1=t[ro<<1].pre1=t[ro<<1|1].sub1=t[ro<<1|1].pre1=0;
        t[ro<<1].sum=t[ro<<1|1].sum=0;
        t[ro<<1].len0=mid-l+1;
        t[ro<<1|1].len0=r-mid;
        t[ro<<1].len1=t[ro<<1|1].len1=0;
        t[ro<<1].rev=t[ro<<1|1].rev=0;//千万记得需要把子节点的翻转标记全部置零 
        t[ro].set=-1;
    }
    else if(t[ro].set==1)
    {
        t[ro<<1].set=t[ro<<1|1].set=t[ro].set;
        t[ro<<1].sum=t[ro<<1].sub1=t[ro<<1].pre1=mid-l+1;
        t[ro<<1|1].sum=t[ro<<1|1].sub1=t[ro<<1|1].pre1=r-mid;
        t[ro<<1].sub0=t[ro<<1].pre0=t[ro<<1|1].sub0=t[ro<<1|1].pre0=0;
        t[ro<<1].len1=mid-l+1;
        t[ro<<1|1].len1=r-mid;
        t[ro<<1].len0=t[ro<<1|1].len0=0;
        t[ro<<1].rev=t[ro<<1|1].rev=0;
        t[ro].set=-1;
    }
    if(t[ro].rev)
    {
        t[ro<<1].rev^=1;
        t[ro<<1|1].rev^=1;
        rever(ro<<1),rever(ro<<1|1);
        t[ro<<1].sum=(mid-l+1-t[ro<<1].sum);
        t[ro<<1|1].sum=(r-mid-t[ro<<1|1].sum);
        t[ro].rev=0;
    }
}
void build(int ro,int l,int r)
{
    t[ro].set=-1;
    if(l==r)
    {
        int tmp;
        scanf("%d",&tmp);
        if(tmp==0)
            t[ro].len0=t[ro].pre0=t[ro].sub0=1;
        else if(tmp==1)
            t[ro].len1=t[ro].pre1=t[ro].sub1=t[ro].sum=1;
        return;
    }
    int mid=l+r>>1;
    build(ro<<1,l,mid);
    build(ro<<1|1,mid+1,r);
    maintain(ro,l,r);
}
void setv(int ro,int L,int R,int set,int l,int r)
{
    if(L==l&&R==r)
    {
        t[ro].rev=0;//注意这里清空了rev,详见reverse函数注释 
        t[ro].set=set;
        if(set==0)
        {
            t[ro].len1=t[ro].sum=t[ro].sub1=t[ro].pre1=0;
            t[ro].len0=t[ro].sub0=t[ro].pre0=r-l+1;
        }
        else
        {
            t[ro].len1=t[ro].sum=t[ro].sub1=t[ro].pre1=r-l+1;
            t[ro].len0=t[ro].sub0=t[ro].pre0=0;
        }
        return;
    }
    pushdown(ro,l,r);
    int mid=l+r>>1;
    if(R<=mid)setv(ro<<1,L,R,set,l,mid);
    else if(L>=mid+1)setv(ro<<1|1,L,R,set,mid+1,r);
    else setv(ro<<1,L,mid,set,l,mid),setv(ro<<1|1,mid+1,R,set,mid+1,r);
    maintain(ro,l,r);
}
void reverse(int ro,int L,int R,int l,int r)
{
    if(L==l&&R==r)
    {
        //若之前有set标记,则rev一定被清空(见setv函数)
        //则rev是在清空的基础上进行叠加,所以pushdown中按时间顺序先set再rev
        //但是这里需要注意一个问题,即这里的前提是set提前把rev清空
        //而子节点很明显还是有rev标记,所以set更新时先清空rev再进行累加的rev操作
        if(t[ro].set!=-1)t[ro].set^=1;
        else t[ro].rev^=1;
        //若在这里直接给rev取反不管set也是可以的,即set完毕之后rev再翻转
        //若进行set标记的思路是直接翻转set标记等价于进行一次翻转,而rev少翻转一次,结果是不变的
        rever(ro);
        t[ro].sum=(r-l+1-t[ro].sum);

        return;
    }
    pushdown(ro,l,r);
    int mid=l+r>>1;
    if(R<=mid)reverse(ro<<1,L,R,l,mid);
    else if(L>=mid+1)reverse(ro<<1|1,L,R,mid+1,r);
    else reverse(ro<<1,L,mid,l,mid),reverse(ro<<1|1,mid+1,R,mid+1,r);
    maintain(ro,l,r);
}
int query_len(int ro,int L,int R,int l,int r)
{
    if(L==l&&R==r)
        return t[ro].len1;
    pushdown(ro,l,r);
    int mid=l+r>>1;
    if(R<=mid)return query_len(ro<<1,L,R,l,mid);
    else if(L>=mid+1)return query_len(ro<<1|1,L,R,mid+1,r);
    else return max(min(mid-L+1,t[ro<<1].sub1)+min(R-mid,t[ro<<1|1].pre1),max(query_len(ro<<1,L,mid,l,mid),query_len(ro<<1|1,mid+1,R,mid+1,r)));
}
int query_sum(int ro,int L,int R,int l,int r)
{
    if(L==l&&R==r)
        return t[ro].sum;
    pushdown(ro,l,r);
    int mid=l+r>>1;
    if(R<=mid)return query_sum(ro<<1,L,R,l,mid);
    else if(L>=mid+1)return query_sum(ro<<1|1,L,R,mid+1,r);
    else return query_sum(ro<<1,L,mid,l,mid)+query_sum(ro<<1|1,mid+1,R,mid+1,r);
}
int main()
{
    int n,q;
    scanf("%d%d",&n,&q);
    build(1,1,n);
    while(q--)
    {
        int d,d1,d2;
        scanf("%d%d%d",&d,&d1,&d2);
        d1++,d2++;
        if(d<=1)
            setv(1,d1,d2,d,1,n);
        else if(d==2)
            reverse(1,d1,d2,1,n);
        else if(d==3)
            printf("%d\n",query_sum(1,d1,d2,1,n));
        else
            printf("%d\n",query_len(1,d1,d2,1,n));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值