P2572 [SCOI2010] 序列操作 线段树维护连续子段和(01串)

这篇文章介绍了如何利用树状数组数据结构解决一个关于区间操作的问题,涉及对连续子段和的维护,包括对1的区间数量、连续1的数量等进行高效查询和更新,展示了在计算机科学竞赛中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

浅调两个钟,终于A掉了,还是不熟练啊

这道题是P4513 小白逛公园 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)的变种

维护的更多,可以说,在维护连续的1,实质上就是维护最大连续子段和。

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

要进行如上的五种操作,我们考虑建立

1.len来记录区间长度(优化)。

2.考虑用tag来维护懒标记,tag=-1时没有操作,tag=0使执行0行为,1同样。

3.用rev记录是否反转(reverse简写)  

4.用b,rb,lb,mb分别记录 区间中1个数 区间左边1连续个数 区间右边1连续个数 最大连续个数

(c同理)

代码如下(有注释)

// Problem: 
//     P2572 [SCOI2010] 序列操作
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2572
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
using namespace std;
const int N=1e5+10;
int a[N];
#define ls u<<1
#define rs u<<1|1
#define endl '\n'
struct Tree{
	int l,r;
	int b,lb,rb,mb,c,lc,rc,mc;//1的区间个数 左边连续个数 右边连续个数 最大连续个数
	int len,tag,rev;//-1  无  0 0 1 1
}tr[N*4];
void mo(int u,int op){
	Tree &t=tr[u];//注意每个地方的t都是要引用的
	if(op==0){
		t.c=t.lc=t.rc=t.mc=t.len;//覆盖
		t.b=t.lb=t.mb=t.rb=0;
		t.rev=0;//要重置 覆盖掉了
		t.tag=0;
	}
	if(op==1){
		t.c=t.lc=t.rc=t.mc=0;
		t.b=t.lb=t.mb=t.rb=t.len;
		t.rev=0;//要重置 覆盖掉了
		t.tag=1;
	}
	if(op==2){
		swap(t.c,t.b);swap(t.rc,t.rb);//取反,所有的都直接交换即可
		swap(t.lc,t.lb);swap(t.mc,t.mb);
		t.rev^=1;
	}
}
void pushup(Tree &t,Tree l,Tree r){//别忘了开引用
	t.b=l.b+r.b;
	t.c=l.c+r.c;
	t.lb=l.c ? l.lb : l.lb+r.lb;  //判断是否有非1的,两种处理方式
	t.lc=l.b ? l.lc : l.lc+r.lc;
	t.rb=r.c ? r.rb : l.rb+r.rb;
	t.rc=r.b ? r.rc : l.rc+r.rc;
	t.mb=max(max(l.mb,r.mb),l.rb+r.lb);
	t.mc=max(max(l.mc,r.mc),l.rc+r.lc);
}
void pushdown(int u){
	Tree &t=tr[u];
	if(t.tag==0) mo(ls,0),mo(rs,0);
	if(t.tag==1) mo(ls,1),mo(rs,1);
	if(t.rev) mo(ls,2),mo(rs,2);
	t.tag=-1;//重置
	t.rev=0;//重置
}

void update(int u,int l,int r,int op){
	if(l<=tr[u].l&&tr[u].r<=r){
		mo(u,op);return;//直接操作
	}
	pushdown(u);//记得下传
	int m=(tr[u].l+tr[u].r)>>1;
	if(l<=m) update(ls,l,r,op);
	if(r>m) update(rs,l,r,op);
	pushup(tr[u],tr[ls],tr[rs]);
}

Tree query(int u,int l,int r){
	if(l<=tr[u].l&&tr[u].r<=r){
		return tr[u];
	}
	pushdown(u);
	int m=(tr[u].l+tr[u].r)>>1;
	if(r<=m) return query(ls,l,r);//这里与平常的写法是不同的
	if(l>m) return query(rs,l,r);
	Tree t;//开一个t结合一下
	pushup(t,query(ls,l,m),query(rs,m+1,r));
	return t;
}

void build(int u,int l,int r){
	int t=a[l];
	tr[u]={l,r,t,t,t,t,t^1,t^1,t^1,t^1,r-l+1,-1,0};//1^1=0 0^1=1
	if(l==r) return;
	int m=(l+r)>>1;
	build(ls,l,m);
	build(rs,m+1,r);
	pushup(tr[u],tr[ls],tr[rs]);
}


int main(){
    int n;cin>>n;int m;cin>>m;
    for(int i=1;i<=n;++i) cin>>a[i];
    build(1,1,n);
    while(m--){
    	int x,y,z;cin>>x>>y>>z;
    	y++,z++;//右移一位方便运算
    	if(x<=2){
    		update(1,y,z,x);
    	}
    	else{
    		Tree t=query(1,y,z);
    		cout<<((x==3) ? (t.b) :(t.mb))<<endl;
    	}
    	
    }
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值