算法发明者:猫锟(巨佬),先贴上他本人的文章:猫树——by 猫锟
猫树的本质:预处理部分区间的答案,需要时直接合并。
那么可以对询问的序列造一个线段树,然后预处理线段树上每一个点所对应的区间的答案,需要时合并即可。
很经典的问题:区间最大子段和。
对于树上的每一个节点,我们维护这样几个元素(设这个节点管理的区间为 l l l~ r r r):
1、
s
l
sl
sl,表示必须选
l
l
l位置上的数,能得到的最大子段和。
2、
s
r
sr
sr,表示必须选
r
r
r位置上的数,能得到的最大子段和。
2.5 、那么实际上我们可以认为,
s
l
sl
sl和
s
r
sr
sr分别表示从左边延伸出来的最大子段和
和右边延伸出来的最大子段和
3、
a
n
s
ans
ans,表示这一段区间内的最大子段和。
4、
s
u
m
sum
sum,表示这一段区间内所有数的和。
那么就得到了这样的转移过程:
sum=zuo->sum+you->sum;//sum的话直接加起来就好
sl=max(zuo->sl,zuo->sum+you->sl);//sl可以直接是左儿子的sl,或者是全选左儿子加上右儿子的sl
sr=max(you->sr,you->sum+zuo->sr);//sr类似
ans=max(zuo->ans,max(you->ans,zuo->sr+you->sl));//ans要么是左儿子的ans,要么是右儿子的ans,要么是左右儿子拼起来后中间的那一段
求解的代码是这样子的:
node *find(int x,int y)
{
if(l==x&&r==y)return this;//如果和询问的区间一样,就返回自己
int mid=l+r>>1;
if(y<=mid)return zuo->find(x,y);//否则去左右找
else if(x>=mid+1)return you->find(x,y);
else//如果在中间,就分两半做
{
node *left=zuo->find(x,mid),*right=you->find(mid+1,y);//记录下左右儿子的答案
node *re=new node();//对左右儿子的答案进行合并
re->sum=left->sum+right->sum;
re->sl=max(left->sl,left->sum+right->sl);
re->sr=max(right->sr,right->sum+left->sr);
re->ans=max(left->ans,max(right->ans,left->sr+right->sl));
return re;
}
}
如果以后发现猫树的神奇应用一定会贴上来的。