P2572 [SCOI2010] 序列操作

P2572 [SCOI2010] 序列操作

分析

这题,论码量在树剖+线段树面前肯定是不够看的,但是单论线段树部分,码量不小,请做好心理准备。

此题需要我们进行的操作有

  1. 0 l r[l, r] 区间内的所有数全变成 0
  2. 1 l r[l, r] 区间内的所有数全变成 1
  3. 2 l r[l,r] 区间内的所有数全部取反,也就是说把所有的 0 变成 1,把所有的 1 变成 0
  4. 3 l r 询问 [l, r] 区间内总共有多少个 1
  5. 4 l r 询问 [l, r] 区间内最多有多少个连续的 1

我们还是老规矩,根据操作,来看我们线段树中需要维护什么。

前三个操作,需要我们维护两个懒标记

  • cov其中-1表示无区间覆盖操作,0/1表示区间覆盖0/1
  • rev,0表示无区间反转操作,1表示有区间反转操作

第四个操作,维护一个sum表示即可。

重点说一下第五个。

第五个操作,求有多少连续的1,那我们自然就联想到求最大子段和的线段树了呀。

那我们维护的东西自然也就得到了。

  • lmx[2],其中lmx[0/1]表示从左起最多的连续0/1的个数
  • rmx[2],其中rmx[0/1]表示从右起最多的连续0/1的个数
  • mx[2],其中mx[0/1]表示区间中最多的连续0/1的个数

这题就考察了对pushdown操作的了解。

对不同的懒标记,pushdown对其余的懒标记的影响,以及对其余变量的影响

这点很关键,尤其容易忘记对其余懒标记的影响。

因为不同懒标记是会相互影响的,一般是有优先级的,因此我们一定要注意,pushdown时,对其余懒标记的操作

我个人的经验是,不论是pushup还是pushdown,其对变量的影响是一样的,因此当影响很多的时候,单独写成一个函数可以有效避免错误

其余的,我们来看代码说话吧。

Ac_code

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct Node
{
    int l,r,sum;
    int lmx[2],rmx[2],mx[2];
    int cov,rev;
}tr[N<<2];
int w[N];
int n,m;

void work(Node &u,Node &l,Node &r,int f)//维护分别对于0,1而言的lmx,rmx,mx
{
    if(l.lmx[f]==l.r-l.l+1) u.lmx[f] = l.r - l.l + 1 + r.lmx[f];
    else u.lmx[f] = l.lmx[f];
    if(r.rmx[f]==r.r-r.l+1) u.rmx[f] = r.r - r.l + 1 + l.rmx[f];
    else u.rmx[f] = r.rmx[f];
    u.mx[f] = max(max(l.mx[f],r.mx[f]),l.rmx[f]+r.lmx[f]);
}

void push(Node &u,Node &l,Node &r)
{
    u.sum = l.sum + r.sum;
    work(u,l,r,0);
    work(u,l,r,1);
}

void pushup(int u)
{
    push(tr[u],tr[u<<1],tr[u<<1|1]);
}

void build(int u,int l,int r)
{
    tr[u] = {l,r};
    tr[u].cov = -1;//全部都要初始化为-1
    if(l==r)
    {
        tr[u].sum = w[l];//到叶节点后初始化
        tr[u].lmx[w[l]] = tr[u].rmx[w[l]] = tr[u].mx[w[l]] = 1;
        return ;
    }
    int mid = l + r >> 1;
    build(u<<1,l,mid),build(u<<1|1,mid+1,r);
    pushup(u);
}

void changecov(Node &u,int k)//pushdown操作中,cov标记对区间的影响
{
    u.lmx[k] = u.rmx[k] = u.mx[k] = u.r - u.l + 1;
    u.lmx[k^1] = u.rmx[k^1] = u.mx[k^1] = 0;
    u.sum = (u.r - u.l + 1)*k;
    u.rev = 0;//记得要将翻转操作的懒标记清空
    u.cov = k;
}

void changerev(Node &u)//pushdown操作中,rev标记对区间的影响,即0,1维护的值翻转
{
    swap(u.lmx[0],u.lmx[1]);swap(u.rmx[0],u.rmx[1]);swap(u.mx[0],u.mx[1]);
    u.sum = u.r - u.l + 1 - u.sum;
    u.rev ^= 1;
}

void pushdown(int u)
{
    auto &root = tr[u],&left = tr[u<<1],&right = tr[u<<1|1];
    if(root.cov!=-1)//注意优先级,我们先处理,区间覆盖操作,原因在modfiy操作中会说
    {
        auto k = root.cov;
        changecov(left,k);
        changecov(right,k);
        root.cov = -1;
    }
    if(root.rev)
    {
        changerev(left);
        changerev(right);
        root.rev = 0;
    }
}

void modify(int u,int l,int r,int k)
{
    if(l<=tr[u].l&&tr[u].r<=r)//区间覆盖操作会直接抹掉之前的一切翻转操作,因此我们先进行区间覆盖
    {
        if(k!=2) 
        {
            changecov(tr[u],k);
            return ;
        }
        changerev(tr[u]);
        return ;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    pushdown(u);
    if(l<=mid) modify(u<<1,l,r,k);
    if(r>mid) modify(u<<1|1,l,r,k);
    pushup(u);
}

Node query(int u,int l,int r)
{
    if(l<=tr[u].l&&tr[u].r<=r) return tr[u];
    int mid = tr[u].l + tr[u].r >> 1;
    pushdown(u);
    if(l>mid) return query(u<<1|1,l,r);
    else if(r<=mid) return query(u<<1,l,r);
    else 
    {
        Node left = query(u<<1,l,r),right = query(u<<1|1,l,r);
        Node res;
        push(res,left,right);
        return res;
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",w+i);
    build(1,1,n);
    while(m--)
    {
        int op,l,r;scanf("%d%d%d",&op,&l,&r);
        ++l,++r;
        if(!op) modify(1,l,r,0);
        else if(op==1) modify(1,l,r,1);
        else if(op==2) modify(1,l,r,2);
        else if(op==3) printf("%d\n",query(1,l,r).sum);
        else printf("%d\n",query(1,l,r).mx[1]);
    }
    return 0;
}
posted @ 2022-05-04 21:19  艾特玖  阅读(83)  评论(0)    收藏  举报