BZOJ1858[Scoi2010]序列操作 题解

题目大意:

有一个01序列,现在对于这个序列有五种变换操作和询问操作: 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。

思路:

维护每一段数的和、左端和右端以及整段中连续的0和1的长度,并使用标记进行下传。

代码:

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<iostream>
  4 #define N 400000
  5 using namespace std;
  6
  7 int root,num,cnt,lmax[2][N],rmax[2][N],mmax[2][N],sum[N],tag[2][N],rev[N],h[N],t[N],rc[N],lc[N],a[N];
  8
  9 void up_date(int x,int k)
 10 {
 11      int l=lc[x],r=rc[x];
 12      lmax[k][x]=lmax[k][l],rmax[k][x]=rmax[k][r];
 13      if (lmax[k][l]==t[l]-h[l]+1) lmax[k][x]+=lmax[k][r];
 14      if (rmax[k][r]==t[r]-h[r]+1) rmax[k][x]+=rmax[k][l];
 15      mmax[k][x]=max(max(mmax[k][l],mmax[k][r]),rmax[k][l]+lmax[k][r]);
 16 }
 17
 18 void build(int l,int r,int &cur)
 19 {
 20      cur=++num,tag[0][cur]=tag[1][cur]=rev[cur]=0;
 21      h[cur]=l,t[cur]=r;
 22      if (l==r)
 23      {
 24          lc[cur]=rc[cur]=0;
 25          lmax[0][cur]=rmax[0][cur]=mmax[0][cur]=(a[l]==0);
 26          lmax[1][cur]=rmax[1][cur]=sum[cur]=mmax[1][cur]=(a[r]==1);
 27          return;
 28      }
 29      int mid=l+r>>1;
 30      build(l,mid,lc[cur]),build(mid+1,r,rc[cur]);
 31      up_date(cur,0),up_date(cur,1),sum[cur]=sum[lc[cur]]+sum[rc[cur]];
 32 }
 33
 34 void mark(int x,int k)
 35 {
 36      tag[k][x]=1,tag[k^1][x]=rev[x]=0,sum[x]=(t[x]-h[x]+1)*k;
 37      lmax[k][x]=rmax[k][x]=mmax[k][x]=t[x]-h[x]+1;
 38      lmax[k^1][x]=rmax[k^1][x]=mmax[k^1][x]=0;
 39 }
 40
 41 void re(int x)
 42 {
 43      rev[x]^=1,sum[x]=t[x]-h[x]+1-sum[x];
 44      swap(lmax[0][x],lmax[1][x]),swap(rmax[0][x],rmax[1][x]),swap(mmax[0][x],mmax[1][x]);
 45 }
 46
 47 void push_down(int x)
 48 {
 49      if (tag[0][x]) mark(lc[x],0),mark(rc[x],0),tag[0][x]=0;
 50      if (tag[1][x]) mark(lc[x],1),mark(rc[x],1),tag[1][x]=0;
 51      if (rev[x]) re(lc[x]),re(rc[x]),rev[x]=0;
 52 }
 53
 54 void change(int l,int r,int cur,int k)
 55 {
 56      if (h[cur]>r || t[cur]<l) return;
 57      if (h[cur]>=l && t[cur]<=r)
 58      {
 59          if (k<2) mark(cur,k);
 60          else re(cur);
 61          return;
 62      }
 63      push_down(cur);
 64      change(l,r,lc[cur],k),change(l,r,rc[cur],k);
 65      up_date(cur,0),up_date(cur,1),sum[cur]=sum[lc[cur]]+sum[rc[cur]];
 66 }
 67
 68 int ask1(int l,int r,int cur)
 69 {
 70      if (h[cur]>r || t[cur]<l) return 0;
 71      if (h[cur]>=l && t[cur]<=r) return sum[cur];
 72      push_down(cur);
 73      return ask1(l,r,lc[cur])+ask1(l,r,rc[cur]);
 74 }
 75
 76 int ask2(int l,int r,int cur)
 77 {
 78     if (l==h[cur] && r==t[cur]) return cur;
 79     int mid=h[cur]+t[cur]>>1;
 80     push_down(cur);
 81     if (r<=mid) return ask2(l,r,lc[cur]);
 82     else if (l>mid) return ask2(l,r,rc[cur]);
 83          else
 84          {
 85              int ans=++cnt;
 86              lc[ans]=ask2(l,mid,lc[cur]),rc[ans]=ask2(mid+1,r,rc[cur]);
 87              up_date(ans,0),up_date(ans,1),sum[ans]=sum[lc[ans]]+sum[rc[ans]];
 88              return ans;
 89          }
 90 }
 91
 92 int main()
 93 {
 94     int n,m,i,x,y,z;
 95     scanf("%d%d",&n,&m);
 96     for (i=1;i<=n;i++) scanf("%d",&a[i]);
 97     build(1,n,root);
 98     for (i=1;i<=m;i++)
 99     {
100         scanf("%d%d%d",&z,&x,&y);
101         x++,y++;
102         if (z==3) printf("%d\n",ask1(x,y,root));
103         else if (z==4) cnt=num,printf("%d\n",mmax[1][ask2(x,y,root)]);
104              else change(x,y,root,z);
105     }
106     return 0;
107 }
时间: 2024-10-23 05:57:51

BZOJ1858[Scoi2010]序列操作 题解的相关文章

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]区间内

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

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

bzoj1858: [Scoi2010]序列操作

lazy-tag线段树. #include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn = 800000 + 10; struct Segtree { #define lc(x) ((x)<<1) #define rc(x) (((x)<<1)|1) int l[maxn],r[maxn]; int l1[maxn],r1[ma

bzoj1858: [Scoi2010]序列操作 线段树

线段树,维护七个值两个标记. 用结构体快了一倍…… #include<bits/stdc++.h> #define N (1<<18) #define M (l+r>>1) #define P (k<<1) #define S (k<<1|1) #define K l,r,k #define L l,M,P #define R M+1,r,S #define Z int l=0,int r=n-1,int k=1 using namespace

bzoj1858 [Scoi2010]序列操作——线段树

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1858 线段树...调了一个上午...(后面带 // 的都是改出来的) lazy 标记的下放好麻烦,还得考虑赋值和取反的先后顺序什么的... 因为在取反时把赋值标记 swap 了,所以下放的时候先判断取反再判断赋值... 而且WA了一上午的原因竟然是一开始不慎把取反以为成翻转了,后来没改干净...那个 rev 的名字啊... 总之没有太改变自己最初的想法.改了些细节就A了还是很高兴的! 代码

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

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

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】序列操作 线段树

1858: [Scoi2010]序列操作 Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 1961  Solved: 991[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 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]区