P2572 [SCOI2010] 序列操作(线段树和珂朵莉树练习)

P2572 [SCOI2010] 序列操作

思路:

比较难的线段树板子。(据说原先甚至可以用珂朵莉树)


线段树的基础实现:

我使用结构体来实现一个segment_tree(线段树)类。

struct segment_tree{
	#define ls p<<1
	#define rs p<<1|1
	
	int n,a[maxn];//可以优化掉

	#undef ls
	#undef rs
}tr;

维护什么信息:

我们要维护一段区间的01序列,需要实现区间赋值为0/1和区间翻转,所以需要两个懒标记,一个标记是否区间赋值,赋值为什么,一个标记是否区间翻转。考虑两个懒标记如何传递。对一个固定的区间进行一系列赋值和翻转操作,两个懒标记信息合并相当于看两个连续的操作序列如何合并,假设分别叫操作 1 , 2 1,2 1,2。发现进行一系列 1 , 2 1,2 1,2 操作后,如果再进行一次操作 1 1 1,相当于所有前面的操作都被清空,再进行操作1。如果进行一次操作 2 2 2,那么相当于只改变一下是否进行操作 2 2 2,不改变前面的操作 1 1 1。因此向子节点传递懒标记的时候,如果传入的懒标记有操作1,直接覆盖懒标记,如果没有,就操作2的懒标记相 异或。代码片段如下(重载+运算符,这里的含义相当于把第二个操作数表示的操作序列接到左边的第一个操作数上):

struct info{
	int l1,l2;
	//l1表示操作1 0不赋值 1赋值为0 2赋值为1  
	//l2表示操作2 0不反转 1翻转
	info(int x=0,int y=0):l1(x),l2(y){};
	info operator+(info x){
		if(!x.l1)return info(l1,l2^x.l2);
		return x;
	}
};

再看要查询什么,要查询1的个数和连续1的最大长度。1的个数好说,合并的时候两边区间相加即可,因为区间01会翻转,因此我们还需要记录0的个数,翻转时两值交换。

连续1的最大长度的区间在两个区间合并的时候,有可能来自左区间,有可能来自右区间,也有可能来自两区间合并时合出来的区间。前两者好说,如果两区间合并把前后相同的1接起来的话,我们就需要维护出来左区间的后缀1的长度,右区间前缀1的长度。不过由于在合并时每个区间都有可能作为左儿子区间或者右儿子区间来进行合并,对一个区间,我们前缀和后缀都需要维护。 因为翻转操作的存在,我们还需要维护 0的最大长度、前缀和后缀0的长度。这样一个线段树节点一共要维护区间的 0/1的个数,0/1的最大长度, 0/1的前缀/后缀长度 八个信息。

struct Node{
	int l,r;//维不维护无所谓
	int ans0,ans1;
	int pre0,pre1,suf0,suf1,mx0,mx1;
	info lazy;
	Node(int x=0){
		lazy=info();
		l=r=ans0=ans1=pre0=pre1=suf0=suf1=mx0=mx1=0;
	};
}tr[maxn<<2];//这里开四倍,两倍概率RE,因为不是正了八经二分,所以可能需要的空间要更大些

至此,所有变量定义就完成了,代码如下:

struct segment_tree{
	#define ls p<<1
	#define rs p<<1|1
	
	int n;
	struct info{
		int l1,l2;
		info(int x=0,int y=0):l1(x),l2(y){};
		info operator+(info x){
			if(!x.l1)return info(l1,l2^x.l2);
			return x;
		}
	};
	struct Node{
		int l,r;
		int ans0,ans1;
		int pre0,pre1,suf0,suf1,mx0,mx1;
		info lazy;
		Node(int x=0){
			lazy=info();
			l=r=ans0=ans1=pre0=pre1=suf0=suf1=mx0=mx1=0;
		};
	}tr[maxn<<2];
	
	...
	
	#undef ls
	#undef rs
}tr;

实现辅助函数:

push_up()或者merge_node()函数

这个函数用来合并两个区间的信息。

考虑如何合并两个区间的信息,一个一个看:

  1. 0的个数:左区间0的个数+右区间0的个数
  2. 1的个数:左区间0的个数+右区间1的个数
  3. 0的最大长度:max{左区间0的最大长度,右区间0的最大长度,左区间0后缀长度+右区间0前缀长度}
  4. 1的最大长度:max{左区间1的最大长度,右区间1的最大长度,左区间1后缀长度+右区间1前缀长度}
  5. 0的前缀长度:
    1. 如果左区间全0:左区间长度+右区间0前缀长度
    2. 如果左区间不全0:左区间0前缀长度
  6. 0的后缀长度:
    1. 如果右区间全0:右区间长度+左区间0后缀长度
    2. 如果右区间不全0:右区间0后缀长度
  7. 1的前缀长度:
    1. 如果左区间全1:左区间长度+右区间1前缀长度
    2. 如果左区间不全1:左区间1前缀长度
  8. 1的后缀长度:
    1. 如果右区间全1:右区间长度+左区间1后缀长度
    2. 如果右区间不全1:右区间1后缀长度

代码片段如下:

Node merge_node(Node a,Node b){
	Node t;
	t.l=a.l;
	t.r=b.r;
	t.ans0=a.ans0+b.ans0;
	t.ans1=a.ans1+b.ans1;
	t.pre0=(a.ans0==a.r-a.l+1)?a.ans0+b.pre0:a.pre0;
	t.pre1=(a.ans1==a.r-a.l+1)?a.ans1+b.pre1:a.pre1;
	t.suf0=(b.ans0==b.r-b.l+1)?a.suf0+b.ans0:b.suf0;
	t.suf1=(b.ans1==b.r-b.l+1)?a.suf1+b.ans1:b.suf1;
	t.mx0=max(max(a.mx0,b.mx0),a.suf0+b.pre0);
	t.mx1=max(max(a.mx1,b.mx1),a.suf1+b.pre1);
	return t;
}
f()修改函数:

用来对一个区间进行上述两个修改,并添加新的懒标记。

节点上挂的懒标记的影响已经处理到这个点上了,现在只需要处理新的懒标记造成的影响,并添加到点的懒标记上,先处理赋值操作,再处理一下翻转操作。

  1. 赋值为0:0的个数、0的最大长度、 0的前后缀长度都改为区间长度,1的个数、1的最大长度、 1的前后缀长度都清0,把新的懒标记加到本来的懒标记上(上面重载了运算符)。
  2. 赋值为1:1的个数、1的最大长度、 1的前后缀长度都改为区间长度,0的个数、0的最大长度、 0的前后缀长度都清0,把新的懒标记加到本来的懒标记上。
  3. 翻转:0/1的个数,0/1的最大长度, 0/1的前后缀长度 各自交换一下。

代码片段如下

void f(int p,int l,int r,info x){
	if(x.l1){
		if(x.l1==1){
			tr[p].mx0=tr[p].pre0=tr[p].suf0=tr[p].ans0=r-l+1;
			tr[p].mx1=tr[p].pre1=tr[p].suf1=tr[p].ans1=0;
		}
		else {
			tr[p].mx0=tr[p].pre0=tr[p].suf0=tr[p].ans0=0;
			tr[p].mx1=tr[p].pre1=tr[p].suf1=tr[p].ans1=r-l+1;
		}
	}
	if(x.l2){
		swap(tr[p].ans0,tr[p].ans1);
		swap(tr[p].pre0,tr[p].pre1);
		swap(tr[p].suf0,tr[p].suf1);
		swap(tr[p].mx0,tr[p].mx1);
	}
	tr[p].lazy=tr[p].lazy+x;
}
push_down()函数:

用来向下传递懒标记,并清空这个区间的懒标记

给两个子区间分别调用上面的修改函数,修改子区间就是传递预先存储的操作序列,进行的操作就是这个区间的懒标记。传递结束之后清空这个区间的懒标记。

代码片段如下

void push_down(int p,int l,int r){
	int mid=(l+r)>>1;
	f(ls,l,mid,tr[p].lazy);
	f(rs,mid+1,r,tr[p].lazy);
	tr[p].lazy=info();
}
build()函数:

到终点的时候把要维护的信息写上,没到终点就向下递归,回来时合并两个子区间。

void build(int p,int l,int r){
	if(l==r){
		tr[p].l=tr[p].r=l;
		int t;
		cin>>t;
		if(t)tr[p].mx1=tr[p].ans1=tr[p].pre1=tr[p].suf1=1;
		else tr[p].mx0=tr[p].ans0=tr[p].pre0=tr[p].suf0=1;
		return;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	tr[p]=merge_node(tr[ls],tr[rs]);
}
void build(int _n){
	n=_n;
	build(1,1,n);
}
print()函数:

调试需要,毕竟线段树很容易出锅。

void print(int p,int l,int r){
	printf("%d [%d,%d]|%d %d\n%2d %2d %2d %2d\n",p,l,r,tr[p].ans1,tr[p].mx1,tr[p].pre0,tr[p].suf0,tr[p].pre1,tr[p].suf1);
	if(l==r)return;//这里没传懒标记,有需要可以传
	int mid=(l+r)>>1;
	print(ls,l,mid);
	print(rs,mid+1,r);
}
void print(){print(1,1,n);}

操作和查询函数:

区间赋值:

如果到达的这个区间被目标修改区间包含,就对这个区间进行修改并返回(f()函数)。如果没有,向下传递懒节点(push_down()函数)并向下递归修改。返回时合并两个儿子区间并更新这个区间的值(merge_node()函数)。

void assign(int p,int l,int r,int L,int R,int x){
	if(L<=l && r<=R){
		f(p,l,r,info(x,0));
		return;
	}
	push_down(p,l,r);
	int mid=(l+r)>>1;
	if(L<=mid)assign(ls,l,mid,L,R,x);
	if(R>mid)assign(rs,mid+1,r,L,R,x);
	tr[p]=merge_node(tr[ls],tr[rs]);
}
区间翻转:

和上面一样,操作信息稍微改一下就行了。

void reverse(int p,int l,int r,int L,int R){
	if(L<=l && r<=R){
		f(p,l,r,info(0,1));
		return;
	}
	push_down(p,l,r);
	int mid=(l+r)>>1;
	if(L<=mid)reverse(ls,l,mid,L,R);
	if(R>mid)reverse(rs,mid+1,r,L,R);
	tr[p]=merge_node(tr[ls],tr[rs]);
}
查询:

查询的思想其实就是合并出答案的区间,我们拿到了这个区间,想知道啥直接从维护的信息中拿就行了。

如果我们现在到达的区间被 询问区间 包含,就直接返回整段区间,如果不包含,顺便把懒标记传下去,再向下查询。看一下左右区间是否包含一部分答案的区间,有就查询一下,没有就不看了,把查询得到的两个区间合并一下然后返回就行了。

Node query(int p,int l,int r,int L,int R){
	if(L<=l && r<=R){
		return tr[p];
	}
	push_down(p,l,r);
	int mid=(l+r)>>1;
	if(R<=mid)return query(ls,l,mid,L,R);
	else if(L>mid)return query(rs,mid+1,r,L,R);
	else return merge_node(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R));
}

注:操作函数因为既要走到深层的线段树节点,又要修改下面的值。所以向下走的时候就需要下传懒标记(push_down()),返回的时候需要更新这个点的值(merge_node())

查询函数如果采用合并区间的思想,不管查询什么直接在返回的区间信息上查询就行了,所以查询函数只需要写一个。查询函数需要走到深层的线段树节点,但是不需要修改下面的值。因此向下走的时候就需要下传懒标记(push_down()),返回的时候需要更新这个点的值


总结一些注意点:

  1. 懒标记存储的实际上是操作信息,而不是值。
  2. 节点上带着的懒标记对这个节点维护的信息已经产生过影响了,所以看这个节点的信息不需要管懒标记,也因此修改函数(f())需要修改完区间的信息再挂懒标记,而不是只挂懒标记
  3. 向下查询的时候才需要将懒标记传下去,上面说了,懒标记就是操作,传懒标记和对区间进行操作实际上是一样的,所以操作函数和传懒标记都会用到修改函数(f())。
  4. 使用 merge_node()函数合并区间的话,既可以用在更新节点的值,也可以用在查询里,而且查询只需要写一个函数,很方便。
  5. 如果你要向深层次线段树节点走,那就需要先传递好懒标记。如果你要修改深层次点的值,需要回溯的时候更新所有经过点的值。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=1e5+5;

int n,m;

struct segment_tree{
	#define ls p<<1
	#define rs p<<1|1
	
	int n;
	struct info{
		int l1,l2;
		//l1 0不赋值 1赋值为0 2赋值为1  
		//l2 0不反转 1翻转
		info(int x=0,int y=0):l1(x),l2(y){};
		info operator+(info x){
			if(!x.l1)return info(l1,l2^x.l2);
			return x;
		}
	};
	struct Node{
		int l,r;
		int ans0,ans1;
		int pre0,pre1,suf0,suf1,mx0,mx1;
		info lazy;
		Node(int x=0){
			lazy=info();
			l=r=ans0=ans1=pre0=pre1=suf0=suf1=mx0=mx1=0;
		};
	}tr[maxn<<2];
	
	void f(int p,int l,int r,info x){
		if(x.l1){
			if(x.l1==1){
				tr[p].mx0=tr[p].pre0=tr[p].suf0=tr[p].ans0=r-l+1;
				tr[p].mx1=tr[p].pre1=tr[p].suf1=tr[p].ans1=0;
			}
			else {
				tr[p].mx0=tr[p].pre0=tr[p].suf0=tr[p].ans0=0;
				tr[p].mx1=tr[p].pre1=tr[p].suf1=tr[p].ans1=r-l+1;
			}
		}
		if(x.l2){
			swap(tr[p].ans0,tr[p].ans1);
			swap(tr[p].pre0,tr[p].pre1);
			swap(tr[p].suf0,tr[p].suf1);
			swap(tr[p].mx0,tr[p].mx1);
		}
		tr[p].lazy=tr[p].lazy+x;
	}
	void push_down(int p,int l,int r){
		int mid=(l+r)>>1;
		f(ls,l,mid,tr[p].lazy);
		f(rs,mid+1,r,tr[p].lazy);
		tr[p].lazy=info();
	}
	Node merge_node(Node a,Node b){
		Node t;
		t.l=a.l;
		t.r=b.r;
		t.ans0=a.ans0+b.ans0;
		t.ans1=a.ans1+b.ans1;
		t.pre0=(a.ans0==a.r-a.l+1)?a.ans0+b.pre0:a.pre0;
		t.pre1=(a.ans1==a.r-a.l+1)?a.ans1+b.pre1:a.pre1;
		t.suf0=(b.ans0==b.r-b.l+1)?a.suf0+b.ans0:b.suf0;
		t.suf1=(b.ans1==b.r-b.l+1)?a.suf1+b.ans1:b.suf1;
		t.mx0=max(max(a.mx0,b.mx0),a.suf0+b.pre0);
		t.mx1=max(max(a.mx1,b.mx1),a.suf1+b.pre1);
		return t;
	}
	void print(int p,int l,int r){
		printf("%d [%d,%d]|%d %d\n%2d %2d %2d %2d\n",p,l,r,tr[p].ans1,tr[p].mx1,tr[p].pre0,tr[p].suf0,tr[p].pre1,tr[p].suf1);
		if(l==r)return;
		int mid=(l+r)>>1;
		print(ls,l,mid);
		print(rs,mid+1,r);
	}
	void print(){print(1,1,n);}
	
	void build(int p,int l,int r){
		if(l==r){
			tr[p].l=tr[p].r=l;
			int t;
			cin>>t;
			if(t)tr[p].mx1=tr[p].ans1=tr[p].pre1=tr[p].suf1=1;
			else tr[p].mx0=tr[p].ans0=tr[p].pre0=tr[p].suf0=1;
			return;
		}
		int mid=(l+r)>>1;
		build(ls,l,mid);
		build(rs,mid+1,r);
		tr[p]=merge_node(tr[ls],tr[rs]);
	}
	void build(int _n){
		n=_n;
		build(1,1,n);
	}
	
	void assign(int p,int l,int r,int L,int R,int x){
		if(L<=l && r<=R){
			f(p,l,r,info(x,0));
			return;
		}
		push_down(p,l,r);
		int mid=(l+r)>>1;
		if(L<=mid)assign(ls,l,mid,L,R,x);
		if(R>mid)assign(rs,mid+1,r,L,R,x);
		tr[p]=merge_node(tr[ls],tr[rs]);
	}
	void reverse(int p,int l,int r,int L,int R){
		if(L<=l && r<=R){
			f(p,l,r,info(0,1));
			return;
		}
		push_down(p,l,r);
		int mid=(l+r)>>1;
		if(L<=mid)reverse(ls,l,mid,L,R);
		if(R>mid)reverse(rs,mid+1,r,L,R);
		tr[p]=merge_node(tr[ls],tr[rs]);
	}
	Node query(int p,int l,int r,int L,int R){
		if(L<=l && r<=R){
			return tr[p];
		}
		push_down(p,l,r);
		int mid=(l+r)>>1;
		if(R<=mid)return query(ls,l,mid,L,R);
		else if(L>mid)return query(rs,mid+1,r,L,R);
		else return merge_node(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R));
	}
	int opt(int l,int r,int op){//方便外部调用的工具人函数
		if(op==0)assign(1,1,n,l,r,1);
		else if(op==1)assign(1,1,n,l,r,2);
		else if(op==2)reverse(1,1,n,l,r);
		else if(op==3)return query(1,1,n,l,r).ans1;
		else return query(1,1,n,l,r).mx1;
		return 0;
	}
	
	#undef ls
	#undef rs
}tr;

int main(){
	cin>>n>>m;
	tr.build(n);
//	tr.print();
	for(int i=1,op,l,r;i<=m;i++){
		cin>>op>>l>>r;
		l++;r++;
		int t=tr.opt(l,r,op);
//		tr.print();
		if(op==3 || op==4){
			cout<<t<<endl;
		}
	}
	return 0;
} 

30分珂朵莉树(不是随机数据,被卡掉了,随机能过)

#include <iostream>
#include <cstdio>
#include <set>
#include <vector>
using namespace std;
const int maxn=1e5+5;

int n,m,a[maxn];

struct kdl_tree{
	#define SIT set<Node>::iterator
	struct Node{
		int l,r,val;
		Node(int l,int r=0,int val=0):l(l),r(r),val(val){};
		bool operator<(const Node x)const{
			return l<x.l;
		}
	};
	set<Node> s;
	
	void build(int n,int a[]){
		for(int i=1;i<=n;i++)
			s.insert(Node(i,i,a[i]));
		s.insert(Node(n+1,n+1,0));
	}
	SIT split(int pos){
		SIT it=s.lower_bound(Node(pos));
		if(it->l==pos){
			return it;
		}
		it--;
		int l=it->l,r=it->r,val=it->val;
		s.erase(it);
		s.insert(Node(l,pos-1,val));
		return s.insert(Node(pos,r,val)).first;
	}
	void assign(int l,int r,int x){
		SIT it2=split(r+1),it1=split(l);
		s.erase(it1,it2);
		s.insert(Node(l,r,x));
	}
	void reverse(int l,int r){
		SIT it2=split(r+1),it1=split(l);
		for(;it1!=it2;it1++)
			it1->val^=1;
	}
	int query(int l,int r){
		SIT it2=split(r+1),it1=split(l);
		int ans=0;
		for(;it1!=it2;it1++){
			if(it1->val==1)
				ans+=it1->r-it1->l+1;
		}
		return ans;
	}
	int qmx(int l,int r){
		SIT it2=split(r+1),it1=split(l);
		int ans=0,res=0;
		for(;it1!=it2;it1++,ans=max(ans,res)){
//			printf("[%d,%d] val=%d %d\n",it1->l,it1->r,it1->val,res );
			if(it1->val==1)res+=it1->r-it1->l+1;
			else res=0;
		}
		return ans;
	}
	void print(){
		for(auto x:s){
			printf("[%d,%d] val=%d\n",x.l,x.r,x.val);
		}
		puts("");
	}
	
	#undef SIT
}tr;

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	tr.build(n,a);
//	tr.print();
	
	for(int i=1,op,l,r;i<=m;i++){
		cin>>op>>l>>r;
		l++;r++;
		if(op==0){
			tr.assign(l,r,0);
		}
		else if(op==1){
			tr.assign(l,r,1);
		}
		else if(op==2){
			tr.reverse(l,r);
		}
		else if(op==3){
			cout<<tr.query(l,r)<<endl;
		}
		else if(op==4){
			cout<<tr.qmx(l,r)<<endl;
		}
//		tr.print();
	}
	
	return 0;
}
  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值