P2253 好一个一中腰鼓!
本蒟蒻第一次用线段树做连续最长子段
线段树是将一个大区间二分成两个小区间,通过递归解决两个小区间的问题,然后合并。得到大区间的解。
类比一下分治法求单个最长连续子段。
每次也都是将一个大区间分成两个小区间。分别解决完小区间后。将小区间合并,从分界点左右遍历。暴力的求连续子序列的长度。
然后将两个小区间分别作为一个区间求解的最优值和从分界点的连续子段长度比较,然后就得到了这个大区间的最长连续子段的长度。
可是,如果在线段树中呢?每次从分界点暴力的求时间复杂度太高。
那能不能预处理出来呢?
答案是肯定的。
我们可以如此想像。对于一次区间。他的最长连续子段能在什么位置呢?
无非就是在左右端点处开始或结束,或者是在区间内部(就是在最长连续子段中有可能没有左右节点)。我们只需要记录这三个值。
在递归合并时求从分界点的连续子段时只需要用从左区间以右端点结尾的连续子段接上右区间以左端点开始的连续子段就可以了。
至于可以接在一起的条件是什么。会在代码中讲解
那什么时候结束呢?也就是递归到了叶子节点(只有一个元素)。
所以我们先定义叶子的状态。
全是1.(左边开始的连续子段的长度,右边结束的连续子段的长度,在,在区间内部的连续子段的长度)
然后更改一个点后,处理他的父节点。如此边回溯边维护就可以了
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
using namespace std;
struct node//线段树结构体
{
int lf;//从左开始的连续子段长度 l:left
int mf;//在区间内部的连续子段长度 m:mid
int rf;//以右端点结束的连续子段的长度 r:right下同
};
node t[70100];//线段树数组
int turn[2]={1,0};//转变数组,也可以自己if
int base[70100];//记录是否被反转过,0为无,1为有
void push(int &root,int &l,int &r,int &m)
{
int ls=root<<1;//left son
int rs=(root<<1)|1;//right son
t[root].lf=t[ls].lf;//大区间的左端点开始的连续子段最起码是左区间以左区间左端点开头的连续子段
if(base[m]!=base[m+1]&&m-l+1==t[ls].lf) //如果左区间整个都是连读的
t[root].lf+=t[rs].lf;//在将右区间的以右区间开头的连续子段长度加上,下同
t[root].rf=t[rs].rf;//处理大区间以右端点结尾的连续子段长度
if(base[m]!=base[m+1]&&r-m==t[rs].rf)
t[root].rf+=t[ls].rf;
t[root].mf=max(t[ls].mf,t[rs].mf);//在大区间内的连续子段,这一步请类比分支
if(base[m]!=base[m+1])//左右区间拼接
t[root].mf=max(t[root].mf,t[ls].rf+t[rs].lf);
return ;
}
void build(int root,int l,int r)
{
if(l==r)//叶子节点的初状态
{
t[root].lf=1;
t[root].mf=1;
t[root].rf=1;
return ;
}
int mid=(l+r)>>1;
build(root<<1,l,mid);
build((root<<1)|1,mid+1,r);
push(root,l,r,mid);//用他两个儿子更新自己,这里用作初始化
return ;
}
void updata(int root,int l,int r,int a)
{
if(l>a||r<a)
return ;
if(l==a&&r==a)
{
base[l]=turn[base[l]];
return ;//更改
}
int mid=(l+r)>>1;
updata(root<<1,l,mid,a);
updata((root<<1)|1,mid+1,r,a);
push(root,l,r,mid);//维护线段树
return ;
}
int main()
{
int n,m;//输入不解释
scanf("%d%d",&n,&m);
build(1,1,n);
int a;
for(int i=1;i<=m;i++)
{
scanf("%d",&a);
updata(1,1,n,a);
printf("%d\n",max(t[1].lf,max(t[1].mf,t[1].rf)));//手动max
}
}