[SCOI2010]序列操作 题解

[SCOI2010]序列操作 题解

前言

这道题很经典,特别是对于线段树的这个板块来说,完成后对线段树的理解会上一个档次。但是说实话,就算知道了这道题的思路,想要完成也很难,因为这个代码实在是太长了,而且线段树维护的变量态度多,很容易把人写晕,所以我就来发这一发题解,点明其中易错的几个点(尤其是我错的),以及每一个函数的详细解答,希望dalao们勿喷。

提示

如果想完成这道题,那么你至少要会以下操作:

  1. 线段树的区间修改,区间查询和(即P3372 【模板】线段树 1)
  2. 线段树的区间修改,区间最长连续的1(即P2894 [USACO08FEB]Hotel G)

言归正传

  • 先说一个我做题的细节,因为原题中的下标是从0开始的,与我们(不包括所有人)的习惯不符合,所以我把每个下标人为地加一,即让线段树的区间从零开始。
  • 紧接着我们来说线段树需要维护的变量:
struct Tree{
   
	int l,r,len,sum0,sum1,lm0,rm0,lm1,rm1,la,num,tag;
}tree[400005];

这里数组开到了4*100000是因为要维护100000个节点,而线段树开数组开四倍维护的节点是比较适合的,不会超空间。

前方高能:这里来说一下维护的每个变量的意思:

  • l:区间的左端点
  • r:区间的右端点
  • len:区间的长度,即r-l+1
  • sum0:区间最长的连续0
  • sum1:区间最长的连续1
  • lm0:区间从左端点起的最长连续0
  • lm1:区间从左端点起的最长连续1
  • rm0:区间从右端点起的最长连续0
  • rm1:区间从右端点起的最长连续1
  • num:区间内1的个数
  • la:区间赋值的懒标记(0表示无操作,1表示赋值为0,2表示赋值为1)
  • tag:区间翻转的懒标记(0表示不翻转,1表示要翻转)

做过P2894 [USACO08FEB]Hotel G的小伙伴们都知道,那道题维护只维护了sum,lm,rm,却没有分0和1,这里分开维护主要是因为取反操作(后面会详细讲解)


首先是push_down操作,即怎样把懒标记下传

inline void push_down(int root){
   
	if(tree[root].la!=0){
   
		if(tree[root].la==1){
   
			tree[root<<1].la=tree[root<<1|1].la=tree[root].la;
			tree[root<<1].num=tree[root<<1].sum1=tree[root<<1].lm1=tree[root<<1].rm1=0;
			tree[root<<1].lm0=tree[root<<1].rm0=tree[root<<1].sum0=tree[root<<1].len;
			tree[root<<1|1].num=tree[root<<1|1].sum1=tree[root<<1|1].lm1=tree[root<<1|1].rm1=0;
			tree[root<<1|1].lm0=tree[root<<1|1].rm0=tree[root<<1|1].sum0=tree[root<<1|1].len;
		} 
		if(tree[root].la==2){
   
			tree[root<<1].la=tree[root<<1|1].la=tree[root].la;
			tree[root<<1].num=tree[root<<1].sum1=tree[root<<1].lm1=tree[root<<1].rm1=tree[root<<1].len;
			tree[root<<1].lm0=tree[root<<1].rm0=tree[root<<1].sum0=0;
			tree[root<<1|1].num=tree[root<<1|1].sum1=tree[root<<1|1].lm1=tree[root<<1|1].rm1=tree[root<<1|1].len;
			tree[root<<1|1].lm0=tree[root<<1|1].rm0=tree[root<<1|1].sum0=0;
		}
		tree[root<<1].tag=tree[root<<1|1].tag=0;
	}
	if (tree[root].tag==1){
   
			tree[root<<1].tag^=1;
			tree[root<<1|1].tag^=1;
			tree[root<<1].num=tree[root<<1].len-tree[root<<1].num;
			tree[root<<1|1].num=tree[root<<1|1].len-tree[root<<1|1].num;
			swap(tree[root<<1].sum1,tree[root<<1].sum0);
			swap(tree[root<<1|1].sum1,tree[root<<1|1].sum0);
			swap(tree[root<<1].lm0,tree[root<<1].lm1);
			swap(tree[root<<1].rm0,tree[root<<1].rm1);
			swap(tree[root<<1|1].lm0,tree[root<<1|1].lm1);
			swap(tree[root<<1|1].rm0,tree[root<<1|1].rm1);
		}
	tree[root].la=0,tree[root].tag=0;
}

额。。。可能稍微有那么一点点长,毕竟有这么多的变量嘛。在这里先声明一个东西,就是这里懒标记中的赋值和翻转的优先顺序,是先赋值,再翻转(比如此时的la=1,tag=1,则这个区间为先覆盖为0,然后翻转,即全为1) 所以下传懒标记时,先讨论覆盖操作:

1.tree[root].la==0: 显然不用进行任何操作

2.tree[root].la==1: 即这个区间的每个值都要赋值成1,那么它的两个儿子区间的值都要赋值成1,所以两个儿子区间的懒标记都是1,然后再来看其它的变量:

  • num: 既然都为0了,那么1的个数也为0.
  • sum1:1都没有了,连续的1也是0个
  • sum0: 现在最长的连续0就是区间长度
  • lm0:整个区间都是0,从左起最长的连续0自然也是区间长度那么多
  • lm1:没有1,lm1=0
  • rm0,lm1同上

3.tree[root].la==2:与tree[root].la==1相反

思考(易错):为什么在下传la的时候,要加一行代码:tree[root<<1].tag=tree[root<<1|1].tag=0;

解答:因为不管前面进行了怎样的操作,覆盖以后就是覆盖的值,这是不可争辩的,所以前面的翻转也就不能作数了。

然后是懒标记tag,由于是翻转所,以0和1互换,所以维护的0和1的值也互换(是不是很好理解),对于num就变成区间长度-num,因为假设原来有num个1,那么就有len-num个0,互换以后就有len-num个1,说到这里,读者便可以理解为什么0和1要分开维护,这样才能在取反时进行维护。重点说一下这里的tag下传,因为这个区间翻转所以它的子区间也要翻转,即代码:

tree[root<<1].tag^=1;
tree[root<<1|1].tag^=1;

这里一定是把子区间的tag异或1而不是赋值成1,因为这个子区间可能原来就需要翻转一次,这次再翻转,就变成原来的样子了。最后清空当前节点的懒标记,那么push_down操作就完美地写成了。

push_up操作,即更改完成子区间后,向上回溯的过程

inline void push_up(int root){
   
	if(tree[root<<1].sum0==tree[root<<1].len){
   
		tree[root].lm0=tree[root<<1].len+tree[root<<
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值