[SCOI2010]序列操作[分块or线段树]

/*
本题的难度在于标记的下放。
下面说一下我的做法:
1.覆盖标记:直接打上就好了
2.取反标记:
    <1>如果有tag标记,将tag标记取反,退出.
    <2>如果有rev标记,直接退出
    <3>无标记,打上rev标记,退出
维护:
sum(当前区间和),lss1(区间从左端点连续1的长度),rss1(区间从右端点连续1的长度),sc1(区间连续1的长度)
lss0,rss0,sc0同理。tag(覆盖标记和取反标记整一起了)
*/
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=1e5+5;
const int M=505;
int a[N],bl[N],n,m,size;
int tag[M],lss0[M],rss0[M],sc0[M],lss1[M],rss1[M],sc1[M],sum[M];
void init(int i){
    tag[i]=-1;lss0[i]=rss0[i]=sc0[i]=lss1[i]=rss1[i]=sc1[i]=sum[i]=0;
    for(int j=(i-1)*size+1;j<=min(n,i*size);j++)
        if(a[j]) sum[i]++;
    int fl=0,p=0;
    for(int j=(i-1)*size+1;j<=min(n,i*size);j++){
        if(!a[j]&&!fl) lss0[i]++;
        else fl=1;
        if(!a[j]) p++;else p=0;
        sc0[i]=max(sc0[i],p);
    }
    fl=0;
    for(int j=min(n,i*size);j>(i-1)*size;j--){
        if(!a[j]&&!fl) rss0[i]++;
        else fl=1;
    }
    fl=0,p=0;
    for(int j=(i-1)*size+1;j<=min(n,i*size);j++){
        if(a[j]&&!fl) lss1[i]++;
        else fl=1;
        if(a[j]) p++;else p=0;
        sc1[i]=max(sc1[i],p);
    }
    fl=0;
    for(int j=min(n,i*size);j>(i-1)*size;j--){
        if(a[j]&&!fl) rss1[i]++;
        else fl=1;
    }
}
void pushdown(int i){
    if(tag[i]==-1) return;
    if(tag[i]==0||tag[i]==1)
        for(int j=(i-1)*size+1;j<=i*size;j++)
            a[j]=tag[i];
    else
        for(int j=(i-1)*size+1;j<=i*size;j++)
            a[j]^=1;
    tag[i]=-1;
}
void cover(int x,int y,int v){
    pushdown(bl[x]);
    for(int i=x;i<=min(y,bl[x]*size);i++) a[i]=v;
    init(bl[x]);
    for(int i=bl[x]+1;i<bl[y];i++){
        tag[i]=v;
        if(v) lss1[i]=rss1[i]=sc1[i]=sum[i]=size,lss0[i]=rss0[i]=sc0[i]=0;
        else lss1[i]=rss1[i]=sc1[i]=sum[i]=0,lss0[i]=rss0[i]=sc0[i]=size;
    }
    if(bl[x]==bl[y]) return;
    pushdown(bl[y]);
    for(int i=(bl[y]-1)*size+1;i<=y;i++) a[i]=v;
    init(bl[y]);
}
void rever(int x,int y){
    pushdown(bl[x]);
    for(int i=x;i<=min(y,bl[x]*size);i++) a[i]^=1;
    init(bl[x]);
    for(int i=bl[x]+1;i<bl[y];i++){
        if(tag[i]==-1) tag[i]=2;
        else if(tag[i]==0) tag[i]=1;
        else if(tag[i]==1) tag[i]=0;
        else tag[i]=-1;
        swap(lss0[i],lss1[i]);swap(rss0[i],rss1[i]);
        swap(sc0[i],sc1[i]);sum[i]=size-sum[i];
    }
    if(bl[x]==bl[y]) return;
    pushdown(bl[y]);
    for(int i=(bl[y]-1)*size+1;i<=y;i++) a[i]^=1;
    init(bl[y]);
}
int query_sum(int x,int y){
    int ans=0;
    pushdown(bl[x]);
    for(int i=x;i<=min(y,bl[x]*size);i++)
        if(a[i]) ans++;
    for(int i=bl[x]+1;i<bl[y];i++) ans+=sum[i];
    if(bl[x]==bl[y]) return ans;
    pushdown(bl[y]);
    for(int i=(bl[y]-1)*size+1;i<=y;i++)
        if(a[i]) ans++;
    return ans;
}
int query_num(int x,int y){
    int ans=0,l=0;
    pushdown(bl[x]);
    for(int i=x;i<=min(y,bl[x]*size);i++){
        if(a[i]) l++;else l=0;
        ans=max(ans,l);
    }
    for(int i=bl[x]+1;i<bl[y];i++){
        l+=lss1[i];
        ans=max(ans,l);
        ans=max(ans,sc1[i]);
        if(lss1[i]!=size) l=rss1[i];
    }
    if(bl[x]==bl[y]) return max(ans,l);
    pushdown(bl[y]);
    for(int i=(bl[y]-1)*size+1;i<=y;i++){
        if(a[i]) l++;else l=0;
        ans=max(ans,l);
    }
    return ans;
}
int main(){
    memset(tag,-1,sizeof(tag));
    scanf("%d%d",&n,&m);
    size=sqrt(n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        bl[i]=(i-1)/size+1;
    }
    for(int i=1;i<=bl[n];i++) init(i);
    for(int i=1,op,x,y;i<=m;i++){
        scanf("%d%d%d",&op,&x,&y);x++;y++;
        if(op==0) cover(x,y,0);
        if(op==1) cover(x,y,1);
        if(op==2) rever(x,y);
        if(op==3) printf("%d\n",query_sum(x,y));
        if(op==4) printf("%d\n",query_num(x,y));
    }
    return 0;
}
时间: 2024-10-29 10:47:53

[SCOI2010]序列操作[分块or线段树]的相关文章

【分块】bzoj1858 [Scoi2010]序列操作

分块 Or 线段树 分块的登峰造极之题 每块维护8个值: 包括左端点在内的最长1段: 包括右端点在内的最长1段: 该块内的最长1段: 该块内1的个数: 包括左端点在内的最长0段://这四个是因为可能有翻转操作,需要swap 0有关的标记 和 1有关的标记 包括右端点在内的最长0段: 该块内的最长0段: 该块内0的个数. 2个懒标记:是否翻转,覆盖成了什么. 怎么处理一个块上有两个标记的情况呢? 若该块原来没有任何标记,或要打的标记和原本的标记种类相同,则直接打上标记: 若已有翻转标记,再覆盖时则

bzoj 1858: [Scoi2010] 序列操作 题解

[原题] 1858: [Scoi2010]序列操作 Time Limit: 10 Sec  Memory Limit: 64 MB Submit: 1031  Solved: 529 [Submit][Status] Description lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0 1 a b 把[a, b]区间内的所有数全变成1 2 a b 把[a,b]区间内

【BZOJ 1858】 [Scoi2010]序列操作

1858: [Scoi2010]序列操作 Time Limit: 10 Sec Memory Limit: 64 MB Submit: 1368 Solved: 712 [Submit][Status][Discuss] Description lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0 1 a b 把[a, b]区间内的所有数全变成1 2 a b 把[a,b]区

bzoj 1858: [Scoi2010]序列操作

1858: [Scoi2010]序列操作 Time Limit: 10 Sec  Memory Limit: 64 MB 线段树,对于每个区间需要分别维护左右中的1和0连续个数,并在op=4时特殊处理一下. Description lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0 1 a b 把[a, b]区间内的所有数全变成1 2 a b 把[a,b]区间内的所有数全

【题解】Luogu P2572 [SCOI2010]序列操作

原题传送门:P2572 [SCOI2010]序列操作 这题好弱智啊 裸的珂朵莉树 前置芝士:珂朵莉树 窝博客里对珂朵莉树的介绍 没什么好说的自己看看吧 操作1:把区间内所有数推平成0,珂朵莉树基本操作 操作2:把区间内所有数推平成1,珂朵莉树基本操作 操作3:把区间内所有数取反(异或1),split后扫描一遍,值域取反 操作4:区间和,珂朵莉树基本操作 操作5:区间最长连续的1,暴力扫描累加 就是这样简单 好像跑的比线段树还快???喵喵喵 #pragma GCC optimize("O3&quo

P2572 [SCOI2010]序列操作

对自己 & \(RNG\) : 骄兵必败 \(lpl\)加油! P2572 [SCOI2010]序列操作 题目描述 lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0 1 a b 把[a, b]区间内的所有数全变成1 2 a b 把[a,b]区间内的所有数全部取反,也就是说把所有的0变成1,把所有的1变成0 3 a b 询问[a, b]区间内总共有多少个1 4 a b 询

1858: [Scoi2010]序列操作

1858: [Scoi2010]序列操作 Description lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0 1 a b 把[a, b]区间内的所有数全变成1 2 a b 把[a,b]区间内的所有数全部取反,也就是说把所有的0变成1,把所有的1变成0 3 a b 询问[a, b]区间内总共有多少个1 4 a b 询问[a, b]区间内最多有多少个连续的1 对于每一

BZOJ 1858 SCOI2010 序列操作 线段树

题目大意:给定一个01序列,提供三种操作: 0:把一段区间的全部元素都变成0 1:把一段区间的全部元素都变成1 2:把一段区间内的全部元素全都取反 3:查询一段区间内1的个数 4:查询一段区间内最长的一段连续的1 首先假设没有操作4这就是bitset的水题... 多了这个,我们考虑线段树 线段树的每个节点存改动标记和翻转标记,以及该区间的信息 尽管查询的信息都是1 可是我们要连0一起保存 由于翻转后0就变成了1 1就变成了0 区间信息包含: 左端点的元素 右端点的元素 左端点開始的最长的连续元素

BZOJ_1858_[Scoi2010]序列操作_线段树

Description lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0 1 a b 把[a, b]区间内的所有数全变成1 2 a b 把[a,b]区间内的所有数全部取反,也就是说把所有的0变成1,把所有的1变成0 3 a b 询问[a, b]区间内总共有多少个1 4 a b 询问[a, b]区间内最多有多少个连续的1 对于每一种询问操作,lxhgww都需要给出回答,聪