上面链接沉了幸亏洛谷还有这道题
无旋treap解题的原理:
1.无旋treap可以在维护平衡的同时保持中序遍历不变(treap也可以,应该都可以),因此我们用下标代表插入二叉树的值,那么我们可以按照中序遍历获得原来的数组。
2.无旋treap的分裂操作可以分裂出包含前k个节点的树和剩下元素的树,就相当于可以把数组切开。
你可以切取想要的区间进行操作,操作完再merge回去即可。
上面讲的很关键,先理解这两点再看下面就简单了。
多BB一句(不一定对,只是个人理解):普通带旋转的treap虽然中序遍历是对的,但是不能像无旋treap这样【抠出一段区间,然后对于这段区间的根节点搞个下放标记就完成操作。】 因此,普通treap不支持区间操作,并且可持久化也很麻烦。而无旋treap可持久化很方便。
这样脑中至少就有了抽象的思路:
插入:从pos切开,将我们要插入的tot个数列先搞成一棵树,然后三棵树按顺序合并。
删除:pos,pos+tot处共切两刀,中间那棵树不要了,旁边两棵树合并
修改:pos,pos+tot处共切两刀,中间那颗树上的点全是要修改的,你有感觉:这里要下放标记,不然整棵改会超时
翻转:同修改
求和:显然上面如果维护对了那么这里鸡毛问题都没有,可以直接维护的。
求和最大的子列:这个玩意儿做线段树的时候碰到过(有兴趣做做 )。
维护三个东西,区间最大前缀和l,区间最大后缀和r,以及我们所需要的区间和最大子列mcs。
我们发现如果我们可以在分裂和合并的时候正确维护信息,就相当于在做区间合并的操作,因此线段树行,平衡树也行!
具体是这样的:
l = max(左儿子l, 左儿子区间和+当前节点的值,左儿子区间和+当前节点的值+右儿子l)
r = max(右儿子r,右儿子区间和+当前节点的值,右儿子区间和+当前节点的值+左儿子r)
mcs = max(左儿子mcs,右儿子mcs,max(左儿子r,0) + 当前节点的值 + max(右儿子l,0))
接下去就是意识流讲解:
一、merge与split
我们先假设push_down(rt)可以正确下放标记并使当前节点的所有需要的信息正确。
再假设update(rt)可以正确更新当前节点的所有信息
毕竟不可能一下子想通整体,只能先假设局部可行了。
下面是结构体内全部的数组
int ch[maxn][2];///左右儿子
int pri[maxn];///用于维护堆性质的优先级
int cnt[maxn];///当前节点包含子节点数量
int val[maxn];///当前节点的值
int sum[maxn];///当前节点所在树的数的总和
int l[maxn],r[maxn],mcs[maxn];///前缀和,后缀和,区间最大和
int rev[maxn],upd[maxn];///两个lazy标记,翻转标记和覆盖标记
int sta[maxn];///用于建树的栈,,,这个可以等会儿再说
int root;
int nextpos[maxn],st,ed;///储存下一个可以用的节点编号,这个是因为空间开不起,最后再看好了,很简单的...
那么有了前面的假设我们的split和merge函数应该这么写:
int merge(int x,int y){///合并x和y
if (!x || !y) return x+y;
push_down(x),push_down(y);
if (pri[x] < pri[y]){///维护大根堆
ch[y][0] = merge(x,ch[y][0]);
update(y);
return y;
}
else {
ch[x][1] = merge(ch[x][1],y);
update(x);
return x;
}
}
int ccnt;
void split(int rt,int k,int& x,int& y){///按