Problem Description
There are a bunch of stones on the beach; Stone color is white or black. Little Sheep has a magic brush, she can change the color of a continuous stone, black to white, white to black. Little Sheep like black very much, so she want to know the longest period of consecutive black stones in a range [i, j].
Input
There are multiple cases, the first line of each case is an integer n(1<= n <= 10^5), followed by n integer 1 or 0(1 indicates black stone and 0 indicates white stone), then is an integer M(1<=M<=10^5) followed by M operations formatted as x i j(x = 0 or 1) , x=1 means change the color of stones in range[i,j], and x=0 means ask the longest period of consecutive black stones in range[i,j]
Output
When x=0 output a number means the longest length of black stones in range [i,j].
Sample Input
4 1 0 1 0 5 0 1 4 1 2 3 0 1 4 1 3 3 0 4 4
Sample Output
1 2 0
题意:
输入n 后面n个数,代表着1-n区间为哪些数。
接着输入m表示有m个操作。
输入0就是查询区间内的1的和;输入1,就把区间内的值0变成1,1变成0(区间内连续0的长度和连续1的长度,数值交换)
参考博客和代码:
线段树区间合并详解 介绍为什么这种问题选择线段树和一些套路
因为自己之前整理的的线段树模板单点,区间 修改和查询,并没有将线段树的节点封装成结构体,所以这次阅读的代码,也是使用了很多很多数组的。
第一次碰到区间合并的问题,需要详细记录一下。
-
数组定义
针对这一题,询问在指定的区间内,长度为1的连续序列的长度是多少?
数组,代表线段树上第个结点 代表的区间内,连续的1的长度是多少;
由于在更新过程中,一旦对区间的数进行翻转更新,连续0的长度就变成了连续1的长度,连续1的长度变成连续0的长度,所以我们同样定义一个数组,代表线段树上第个结点 代表的区间内,连续0的长度是多少。
在对 孩子节点 Pushup( )得到父节点时,父节点代表的区间内,连续1的长度可能是:
①左孩子中连续1的长度
②右孩子中连续1的长度
③可能跨越左孩子和右孩子。(两个孩子手牵手的赶脚)
在第③种情况下,我们就得知道,左孩子代表的区间,从右向左数有多少个连续1?右孩子代表的区间,从左向右数有多少个连续1?
所以定义数组:
表示,线段树上第个结点 代表的区间内,从左向右数有几个连续的0。姑且称之为“左连续0”
表示,线段树上第个结点 代表的区间内,从左向右数有几个连续的1。“左连续1”
表示,线段树上第个结点 代表的区间内,从右向左数有几个连续的0。“右连续0”
表示,线段树上第个结点 代表的区间内,从右向左数有几个连续的1。“右连续1”
还有个表示原数组。 是懒惰标记。
const int maxn=1e5+5;
int n;
int num[maxn];//存储原数组
int tree0[maxn<<2];//当前节点所代表的区间,连续0的长度
int tree1[maxn<<2];//...1的长度
int left0[maxn<<2];//当前节点代表的区间,从左往右数连续0的长度
int left1[maxn<<2];//...1的长度
int right0[maxn<<2];//当前节点代表的区间,从右往左数连续0的长度
int right1[maxn<<2];//...1的长度
int lazy[maxn<<2];//懒惰标记
-
pushup( ) 函数
void pushup(int root,int left,int right)
{
int mid=(left+right)/2;
int ln=mid-left+1;//左边叶子节点的个数
int rn=right-mid;
left0[root]=left0[2*root];
left1[root]=left1[2*root];
if(left0[root]==ln)
left0[root]+=left0[2*root+1];
if(left1[root]==ln)
left1[root]+=left1[2*root+1];
right0[root]=right0[2*root+1];
right1[root]=right1[2*root+1];
if(right0[root]==rn)
right0[root]+=right0[2*root];
if(right1[root]==rn)
right1[root]+=right1[2*root];
tree0[root]=max( max(tree0[2*root],tree0[2*root+1]), right0[2*root]+left0[2*root+1] );
tree1[root]=max( max(tree1[2*root],tree1[2*root+1]), right1[2*root]+left1[2*root+1] );
return;
}
pushup( ) 函数,利用孩子节点向上推出父节点的各项数据。
我们线段树上的每个结点都有6个属性,对应了6个数组。(哦,还有一个懒惰标记,就算7个叭)。
对于 ,父节点一定可以先更新为左孩子的“左连续0”。但是父节点的“左连续0”一定等于左孩子的“左连续0”吗?如果左孩子全部都是0(),右孩子的区间开始也有0怎么办?这个时候,就要加上右孩子的“左连续0”了。
1也同理。right0/1也同理。
最后,更新。如上面所说的三种情况,左孩子中连续0的个数,右孩子中连续0的个数,左右孩子联手(左孩子的“右连续”+右孩子的“左连续”)。取最大值。
-
build( ) 函数
build( ) 函数用于建立线段树。有了Pushup函数之后,build函数与之前的线段树建立并无太大差别。
void build(int root,int left,int right)
{
lazy[root]=0;
left0[root]=right0[root]=tree0[root]=0;
left1[root]=right1[root]=tree1[root]=0;
if(left==right)
{
if(num[left]==1)
{
left1[root]=right1[root]=tree1[root]=1;
left0[root]=right0[root]=tree0[root]=0;
}
else
{
left1[root]=right1[root]=tree1[root]=0;
left0[root]=right0[root]=tree0[root]=1;
}
return;
}
int mid=(left+right)/2;
build(root*2,left,mid);
build(root*2+1,mid+1,right);
pushup(root,left,right);
}
开始,该节点的所有属性初始化为0。
当函数走到叶子节点的时候,也就是(left==right)的时候,通过原数组的数值,更新连续区间的6个“连续”属性。
也有简便的写法:
if(left==right)
{
int j;
scanf("%d",&j);
left1[rt]=right1[rt]=tree1[rt]= j;//简便写法.
left0[rt]=right0[rt]=tree0[root]= !j;
return;
}
-
pushdown( ) 函数
与lazy[ ]懒惰标记有关,父节点的更新,在必要时传给子节点。
void Swap(int root)
{
swap(tree0[root],tree1[root]);
swap(left0[root],left1[root]);
swap(right0[root],right1[root]);
}
void pushdown(int root)
{
if(lazy[root])
{
lazy[2*root] ^=1;
lazy[2*root+1] ^=1;
lazy[root]=0;
Swap(2*root);
Swap(2*root+1);
}
}
数组,其实只需要0和1来表示就可以了。因为翻转了一次()之后,再翻转一次(一共翻转2次),等于翻转0次。 与1进行^,异或运算,就是取反了。
如果父节点有懒惰标记为1(0说明翻转了偶数次,跟不翻的结果相同),(如果接下来涉及到子节点查询或者更新了,就是不能再懒了的时候),将我自己的父节点的标记传下去。父节点包含了左孩子和右孩子代表的区间,这两个区间懒惰标记取反,意思是孩子们有这个任务区间查询或翻转的任务了,紧接着进行孩子们的交换操作。最后,清除自己的懒惰标记,因为我已经把事情完成了,左孩子交换了,右孩子交换了,也就是我自己父节点代表的区间完成了交换。
-
update( )函数
update( )函数,用于更新。代表更新的左右区间。
void update(int root,int left,int right,int uleft,int uright)
{
if(left>=uleft && right<=uright)
{
lazy[root] ^=1;
Swap(root);
return;
}
int mid=(left+right)/2;
pushdown(root);
if(mid>=uleft)
update(2*root,left,mid,uleft,uright);
if(mid<uright)
update(2*root+1,mid+1,right,uleft,uright);
pushup(root,left,right);
}
如果(当前区间)(查询区间),进行操作。操作包括:
①更新懒惰标记(标记一下我这里交换过一次了,孩子我还懒得不想动。。23333)
②交换0和1的3个连续区间。这里是我root的交换,,虽然其实已经到整个线段树的最下面一层叶子节点了。
否则,如果,这个时候我必须得动一下自己的孩子节点了。先Pushdown( ),告诉孩子们我刚刚经历了什么,然后递归更新左边区间,递归更新右边区间。更新完了之后,从新的孩子节点中,得到我自己父节点的新的数值。
-
query( )函数
查询区间内,最大的连续1的长度。
int query(int root,int left,int right,int qleft,int qright)
{
if(left>=qleft && right<=qright)
{
return tree1[root];
}
int mid=(left+right)/2;
pushdown(root);
if(qright<=mid)//全在左子树
return query(2*root,left,mid,qleft,qright);
if(qleft>mid)//全在右子树
return query(2*root+1,mid+1,right,qleft,qright);
int ll=query(2*root,left,mid,qleft,qright);
int rr=query(2*root+1,mid+1,right,qleft,qright);
int lr=min(right1[2*root],mid-qleft+1)+min(left1[2*root+1],qright-mid);
return max(max(ll,rr),lr);
}
如果(当前区间)(查询区间), 直接返回这个区间内的连续1的长度。
否则,说明还没到底嘛。
如果查询区间全部在左子树,递归查询。
查询区间全在右子树上,递归查询。
最怕的呢,就是横跨两个子树的区间,取最大值。左孩子连续1的长度(),右孩子上连续1的长度(),联手的两个孩子连续1的长度( ),都有可能最大。根据我们pushup()函数的思想,联手的两个孩子连续1的长度=左孩子的“右连续1”+右孩子的“左连续1”。
哦,是吗?
并不!
左孩子的“右连续1”,如果有3个,但是我要查询的区间就包括了左孩子的一个点,那不就错了吗?
也即,左子树的“右连续1”个数,不能大于他要 查询的左区间()---- 左孩子代表的区间的右端点 中所有结点个数(个)。两者取较小值。
-
最后的代码
//AC
//HDU 3911
#include<iostream>
#include<algorithm>
#include<set>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<stdio.h>
using namespace std;
const int maxn=1e5+5;
int n;
int num[maxn];//存储原数组
int tree0[maxn<<2];//当前节点所代表的区间,连续0的长度
int tree1[maxn<<2];//...1的长度
int left0[maxn<<2];//当前节点代表的区间,从左往右数连续0的长度
int left1[maxn<<2];//...1的长度
int right0[maxn<<2];//当前节点代表的区间,从右往左数连续0的长度
int right1[maxn<<2];//...1的长度
int lazy[maxn<<2];//懒惰标记
void pushup(int root,int left,int right)
{
int mid=(left+right)/2;
int ln=mid-left+1;//左边叶子节点的个数
int rn=right-mid;
left0[root]=left0[2*root];
left1[root]=left1[2*root];
if(left0[root]==ln)
left0[root]+=left0[2*root+1];
if(left1[root]==ln)
left1[root]+=left1[2*root+1];
right0[root]=right0[2*root+1];
right1[root]=right1[2*root+1];
if(right0[root]==rn)
right0[root]+=right0[2*root];
if(right1[root]==rn)
right1[root]+=right1[2*root];
tree0[root]=max( max(tree0[2*root],tree0[2*root+1]), right0[2*root]+left0[2*root+1] );
tree1[root]=max( max(tree1[2*root],tree1[2*root+1]), right1[2*root]+left1[2*root+1] );
return;
}
void build(int root,int left,int right)
{
lazy[root]=0;
left0[root]=right0[root]=tree0[root]=0;
left1[root]=right1[root]=tree1[root]=0;
if(left==right)
{
if(num[left]==1)
{
left1[root]=right1[root]=tree1[root]=1;
left0[root]=right0[root]=tree0[root]=0;
}
else
{
left1[root]=right1[root]=tree1[root]=0;
left0[root]=right0[root]=tree0[root]=1;
}
return;
}
int mid=(left+right)/2;
build(root*2,left,mid);
build(root*2+1,mid+1,right);
pushup(root,left,right);
}
void Swap(int root)
{
swap(tree0[root],tree1[root]);
swap(left0[root],left1[root]);
swap(right0[root],right1[root]);
}
void pushdown(int root)
{
if(lazy[root])
{
//lazy[2*root] ^=lazy[root];//lazy[root]必然等于1
// lazy[2*root+1] ^=lazy[root];
lazy[2*root] ^=1;
lazy[2*root+1] ^=1;
lazy[root]=0;
Swap(2*root);
Swap(2*root+1);
}
}
void update(int root,int left,int right,int uleft,int uright)
{
if(left>=uleft && right<=uright)
{
lazy[root] ^=1;
Swap(root);
return;
}
int mid=(left+right)/2;
pushdown(root);
if(mid>=uleft)
update(2*root,left,mid,uleft,uright);
if(mid<uright)
update(2*root+1,mid+1,right,uleft,uright);
pushup(root,left,right);
}
int query(int root,int left,int right,int qleft,int qright)
{
if(left>=qleft && right<=qright)
{
return tree1[root];
}
int mid=(left+right)/2;
pushdown(root);
if(qright<=mid)//全在左子树
return query(2*root,left,mid,qleft,qright);
if(qleft>mid)//全在右子树
return query(2*root+1,mid+1,right,qleft,qright);
int ll=query(2*root,left,mid,qleft,qright);
int rr=query(2*root+1,mid+1,right,qleft,qright);
int lr=min(right1[2*root],mid-qleft+1)+min(left1[2*root+1],qright-mid);
return max(max(ll,rr),lr);
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++)
scanf("%d",&num[i]);
build(1,1,n);
int m;
scanf("%d",&m);
while(m--)
{
int choice,left,right;
scanf("%d%d%d",&choice,&left,&right);
if(choice==0)
{
int res=query(1,1,n,left,right);
printf("%d\n",res);
}
else
{
update(1,1,n,left,right);
}
}
}
}
希望自己这样理解是没毛病的。恳请大佬们指正。
最后,转一下国立老师养的锦鲤。国立老师怎么养个鱼,都能拿全国冠军??
我常常因为爱豆的努力和敬业,而感到非常的羞愧。ε=(´ο`*)))唉