无旋treap大法好
无旋Treap大法好
原理?
- 是一棵二叉查找树: 一个节点左子树权值都比他小,右子树权值都比他大
- 所以可以维护序列(以位置为权值),或数值(以数值为权值)
- 是一个堆: 每个节点除了上述提到的权值外,还有一个随机生成的给堆用的优先级
- 好像有了随机+堆就可以使树高均摊\(O(log_2^n)\)?,怎么证明呢?
用分裂和合并两个操作为基础,写起来会比较清真
基本操作
结构体的组成
struct Treap
{
LL val, pri; //数值, 优先级
int cnt, siz, ch[2]; // 计数, 子树大小, 左右儿子
//还可以加一些标记,如这个子树翻转的标记
Treap() {}
Treap(int _val) { val = _val, pri = rand(), cnt = siz = 1, ch[0] = ch[1] = 0; }
} t[MAXN];
split(按数值)
这两个分裂的代码看了很久才看懂
split(int now, int val, int & x, int & y)
意思是将\(now\)为根的这棵子树, \(<=val\)的部分分裂, 并将他的根记录在\(x\)上, \(y\)同理
那么怎么递归实现呢?
主要思路是一旦碰到能整棵子树都\(或>val 或<=val\)就整棵一起分割
注意在当前子树的根\(now\)分裂的时候, 只能确保一边的整棵子树完全归属\(<=val\)或\(>val\)的部分
例如当val < t[now].val
, 只能确定整棵右子树及\(now\)都\(>val\),所以现将\(y\)赋值为他们的根(\(now\)),
但是在左子树仍可能有\(>val\)的节点, 所以将\(now\)的左子作为参数传递, 意在后来分离出的\(>val\)的节点接在\(now\)上
而现在还并不能分离\(<=val\)的某棵子树, 于是将传递过来的\(x\)继续传下去
另一种情况类似
void split(int now, int val, int & x, int & y) // <=val --> x, > val --> y
{
if (!now) return (void)(x = y = 0);
if (val < t[now].val) y = now, split(ls, val, x, ls);
else x = now, split(rs, val, rs, y);
update(now);
}
split(按大小)
void split_k(int now, int k, int & x, int & y) // 当前分割跟为now的子树, 并将<=k的根连到x(赋值给x),y同理
{
if (!now) return (void)(x = y = 0); //不断往下分,分完了
if (k <= t[ls].siz) y = now, split_k(ls, k, x, ls); // 确定根及右子树都>k,所以先分出他们,然后去分左子树, 将剩下的>k连回左子,<=k的连向x
else x = now, split_k(rs, k - t[ls].siz - 1, rs, y); // 确定根及左子树<=k, 分出他们,然后分右子树,将右子树中<=k的连回右子,>k的连向y
update(now);
}
merge
int merge(int x, int y) // max_x < min_y
{
if (!x || !y) return x + y;
if (t[x].pri < t[y].pri)
{
t[x].ch[1] = merge(t[x].ch[1], y);
update(x);
return x;
}
else
{
t[y].ch[0] = merge(x, t[y].ch[0]);
update(y);
return y;
}
}
LL kth(int k)
{
int now = root;
while (1)
{
if (t[rs].siz >= k) now = rs;
else if (t[rs].siz + 1 < k) k -= t[rs].siz + 1, now = ls;
else return t[now].val;
}
}
例题1
BZOJ1500,NOI2005,维修数列
用这道毒瘤题具体分析一些\(Treap\)支持的操作
题意概述
维护一个数列,
要求区间插入/删除/统一修改/翻转/求和, 求当前整个数列最大子段和(至少包含一个数)
任何时刻数列中最多含有500 000个数,数列中任何一个数字均在[-1 000, 1 000]内。
插入的数字总数不超过4 000 000个,输入文件大小不超过20MBytesN 和M(M ≤20 000
样例
Sample Input
9 8
2 -6 3 5 1 -5 -3 6 3
GET-SUM 5 4
MAX-SUM
INSERT 8 3 -5 7 2
DELETE 12 1
MAKE-SAME 3 3 2
REVERSE 3 6
GET-SUM 5 4
MAX-SUMSample Output
-1
10
1
10
\(Treap\)的具体操作
\(O(n)\)建树
主要用于对数列的建树, 比一个一个插入优良一点
思路是: 对于一个新的节点, 他一定在之前节点构成的\(Treap\)的右边(废话),
所以想象一下把他从右边插入, 找到最右边那条链上第一个堆的优先级比他大的位置, 把他和他的整棵子树切下来, 把新节点换上去, 再结回新节点的左儿子
可以用栈实现
int newnode(int val)
{
int now = renode ? recyc[renode --] : ++totnode; // 循环利用
t[now] = Treap(val);
return now;
}
int build(int tot)
{
for (int i = 1; i <= tot; ++ i)
{
int now = newnode(in()), rec = 0;
while (top && t[now].pri < t[stk[top]].pri)
update(stk[top]), rec = stk[top --];
if (top) t[stk[top]].ch[1] = now;
t[now].ch[0] = rec;
stk[++top] = now;
}
while (top) update(stk[top --]);
return stk[1];
}
分离出一个区间
例如将\([l, r]\)分离到\(y\)上
int x = 0, y = 0, z = 0;
split(root, l - 1, x, y); // x <= l-1, y >= l
split(y, r - l + 1, y, z); // y [l, r]
insert一个点
int newnode(int val)
{
t[++tot] = Treap(val);
return tot;
}
void insert(int val)
{
int x = 0, y = 0, xx = 0, xy = 0;
split(root, val, x, y);
root = merge(merge(x, newnode(val)), y);
}
insert一个区间
void insert(int pos, int tot) // ok
{
int x, y;
split(root, pos, x, y);
root = merge(merge(x, build(tot)), y);
}
delete一个区间
void delet(int pos, int tot)
{
int x, y, z;
split(root, pos - 1, x, y);
split(y, tot, y, z);
root = merge(x, z);
eat(y); // 回收
}
第k大
LL kth(int k)
{
int now = root;
while (1)
{
if (t[rs].siz >= k) now = rs;
else if (t[rs].siz + 1 < k) k -= t[rs].siz + 1, now = ls;
else return t[now].val;
}
}
关于打标记
因为要保证update(now)
时左右两个儿子的信息已经更新, 所以标记代表本节点已经修改,子树需要修改,这样比较好
下面就是两种标记
区间翻转
先分离出要翻转的区间, 打上标记
不用担心之后这个有标记的根不是恰好代表这段区间
因为之后的合并/分裂操作, 一旦需要改变某个节点\(x\)在树中的结构, 就会立马下传标记, 并且之后会向上更新
void markrev(int now) // 标记这个子树需要翻转, 意味着子树内每个点左右儿子都交换
{
if (!now) return ;
t[now].rev ^= 1;
swap(t[now].pre, t[now].suf); // 用于维护区间最大子段和, 区间交换那么前缀和后缀也要交换
swap(ls, rs);
}
void reverse(int l, int r)
{
int x = 0, y = 0, z = 0;
split(root, l - 1, x, y); // x <= l-1, y >= l
split(y, r - l + 1, y, z); // y [l, r]
markrev(y); // !
root = merge(merge(x, y), z);
}
区间赋值
和翻转类似
void markcov(int now, int cov)
{
if (!now) return ;
t[now].val = t[now].cov = cov;
t[now].sum = t[now].val * t[now].siz;
t[now].pre = t[now].suf = max(t[now].sum, 0);
t[now].maxsum = max(t[now].sum, t[now].val);
}
void modify(int l, int r, int c)
{
int x, y, z;
split(root, l - 1, x, y);
split(y, r - l + 1, y, z);
markcov(y, c); // !
root = merge(merge(x, y), z);
}
区间求和
每个节点维护子树内的权值就可以了, 具体的维护下面区间修改的更新中说明
int getsum(int l, int r)
{
int x, y, z, ret;
split(root, l - 1, x, y);
split(y, r - l + 1, y, z);
ret = t[y].sum;
root = merge(merge(x, y), z);
return ret;
}
\(和相关pushdown() 和 update() 相关\)
之前已经说过, 打标记时就修改此节点的信息, 而标记表示子树需要修改
单独写了加上标记的函数(结合之前几段代码)
void pushdown(int now)
{
if (!now) return ;
if (t[now].rev)
{
markrev(ls), markrev(rs);
t[now].rev = 0;
}
if (t[now].cov != 10000) //
{
markcov(ls, t[now].cov), markcov(rs, t[now].cov);
t[now].cov = 10000;
}
}
更新是确保子树信息正确
void update(int now) // ls ans rs 's info must be right
{
t[now].siz = t[ls].siz + t[rs].siz + 1;
t[now].sum = t[ls].sum + t[rs].sum + t[now].val;
t[now].pre = max(max(t[ls].pre, t[ls].sum + t[now].val + t[rs].pre), 0);
t[now].suf = max(max(t[rs].suf, t[rs].sum + t[now].val + t[ls].suf), 0);
t[now].maxsum = t[now].val + t[ls].suf + t[rs].pre;
if (ls) t[now].maxsum = max(t[now].maxsum, t[ls].maxsum);
if (rs) t[now].maxsum = max(t[now].maxsum, t[rs].maxsum);
}
垃圾回收, 节省空间
int newnode(int val)
{
int now = renode ? recyc[renode --] : ++totnode;
t[now] = Treap(val);
return now;
}
void eat(int now)
{
if (!now) return ;
eat(ls); eat(rs);
recyc[++renode] = now;
}
void delet(int pos, int tot)
{
int x, y, z;
split(root, pos - 1, x, y);
split(y, tot, y, z);
root = merge(x, z);
eat(y);
}
BZOJ1500 代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAXN = 5e5 + 10;
inline int in()
{
int x = 0, flag = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') flag = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return x * flag;
}
int n, m;
#define ls (t[now].ch[0])
#define rs (t[now].ch[1])
int root, totnode;
int recyc[MAXN], renode;
struct Treap
{
int val, pri, siz, ch[2], rev, cov, sum, pre, suf, maxsum;
Treap() {}
Treap(int _val)
{
sum = val = _val, pri = rand(), siz = 1, ch[0] = ch[1] = rev = 0;
maxsum = pre = suf = max(0, val);
cov = 1e4;
}
} t[MAXN];
void markrev(int now) //ok
{
if (!now) return ;
t[now].rev ^= 1;
swap(t[now].pre, t[now].suf);
swap(ls, rs);
}
void markcov(int now, int cov) // ok
{
if (!now) return ;
t[now].val = t[now].cov = cov;
t[now].sum = t[now].val * t[now].siz;
t[now].pre = t[now].suf = max(t[now].sum, 0);
t[now].maxsum = max(t[now].sum, t[now].val);
}
void pushdown(int now)
{
if (!now) return ;
if (t[now].rev)
{
markrev(ls), markrev(rs);
t[now].rev = 0;
}
if (t[now].cov != 10000) //
{
markcov(ls, t[now].cov), markcov(rs, t[now].cov);
t[now].cov = 10000;
}
}
void update(int now) // ls ans rs 's info must be right
{
t[now].siz = t[ls].siz + t[rs].siz + 1; // ok
t[now].sum = t[ls].sum + t[rs].sum + t[now].val; // ok
t[now].pre = max(max(t[ls].pre, t[ls].sum + t[now].val + t[rs].pre), 0);
t[now].suf = max(max(t[rs].suf, t[rs].sum + t[now].val + t[ls].suf), 0);
t[now].maxsum = t[now].val + t[ls].suf + t[rs].pre;
if (ls) t[now].maxsum = max(t[now].maxsum, t[ls].maxsum);
if (rs) t[now].maxsum = max(t[now].maxsum, t[rs].maxsum);
//pay attention
}
void split(int now, int k, int & x, int & y)
{
if (!now) return (void)(x = y = 0);
pushdown(now);
if (k <= t[ls].siz) y = now, split(ls, k, x, ls);
else x = now, split(rs, k - t[ls].siz - 1, rs, y);
update(now);
}
int merge(int x, int y) // max_x < min_y
{
pushdown(x); pushdown(y);
if (!x || !y) return x + y;
if (t[x].pri < t[y].pri)
{
t[x].ch[1] = merge(t[x].ch[1], y);
update(x);
return x;
}
else
{
t[y].ch[0] = merge(x, t[y].ch[0]);
update(y);
return y;
}
}
int newnode(int val)
{
int now = renode ? recyc[renode --] : ++totnode;
t[now] = Treap(val);
return now;
}
int stk[MAXN], top;
int build(int tot) // ok
{
for (int i = 1; i <= tot; ++ i)
{
int now = newnode(in()), rec = 0;
while (top && t[now].pri < t[stk[top]].pri)
update(stk[top]), rec = stk[top --];
if (top) t[stk[top]].ch[1] = now;
t[now].ch[0] = rec;
stk[++top] = now;
}
while (top) update(stk[top --]);
return stk[1];
}
void insert(int pos, int tot) // ok
{
int x, y;
split(root, pos, x, y);
root = merge(merge(x, build(tot)), y);
}
void eat(int now)
{
if (!now) return ;
eat(ls); eat(rs);
recyc[++renode] = now;
}
void delet(int pos, int tot)
{
int x, y, z;
split(root, pos - 1, x, y);
split(y, tot, y, z);
root = merge(x, z);
eat(y);
}
int getsum(int l, int r) // ok
{
int x, y, z, ret;
split(root, l - 1, x, y);
split(y, r - l + 1, y, z);
ret = t[y].sum;
root = merge(merge(x, y), z);
return ret;
}
int maxsum() //ok
{
return t[root].maxsum;
}
void modify(int l, int r, int c) // ok
{
int x, y, z;
split(root, l - 1, x, y);
split(y, r - l + 1, y, z);
markcov(y, c);
root = merge(merge(x, y), z);
}
void reverse(int l, int r) // ok
{
int x = 0, y = 0, z = 0;
split(root, l - 1, x, y); // x <= l-1, y >= l
split(y, r - l + 1, y, z); // y [l, r]
markrev(y);
root = merge(merge(x, y), z);
}
#undef ls
#undef rs
char opt[15];
int main()
{
n = in(), m = in();
root = build(n);
int pos, tot;
while (m --)
{
scanf("%s", opt + 1);
if (opt[3] == 'X') // MAX-SUM 当前最子段和
printf("%d\n", maxsum());
else
{
pos = in(), tot = in();
if (opt[1] == 'G') // GET-SUM 从pos开始tot个数的和
printf("%d\n", getsum(pos, pos + tot - 1));
else if (opt[1] == 'I') // INSERT 在pos之后插入a[1~tot]
insert(pos, tot);
else if (opt[1] == 'D') // DELETE 删除从pos开始tot位
delet(pos, tot);
else if (opt[3] == 'K') // MAKE-SAME 将pos开始tot位修改为c
modify(pos, pos + tot - 1, in());
else if (opt[1] == 'R') // REVERSE 翻转从pos开始tot位
reverse(pos, pos + tot - 1);
}
}
return 0;
}
/*201907122010 ~ 201907131247*/