DS Trick
Tangninghaha · · 算法·理论
元旦快乐!
题单
线段树分裂与合并
动态开点线段树
堆式线段树采用点
例如:
-
如果一棵线段树只有一条右链,即每个节点是父亲的右儿子,右链的叶子上存储了信息,那么这个节点的编号是
O(n) 级别的,然而树上有用节点只有O(\log n) 个。 -
如果维护一棵值域是
10^{9} 的权值线段树,使用堆式建树有O(V) 个节点,显然不可接受。然而每一次操作只会访问到O(\log V) 个节点,在q 次操作下,有用节点实际上只有O(q\log v) 个。
上述两种情况说明,在堆式建树可能会产生无效节点。
不再认为点
当访问到一个标号为
比如说单点修改可以这么写(将 p 的值改为 v):
int chg(int p, int v, int id, int l, int r) {
if (!id) id = ++cnt;
if (l == r) {
// update information
return id;
}
int mid = (l + r) >> 1;
if (p <= mid) ls[id] = chg(p, v, ls[id], l, mid);
else rs[id] = chg(p, v, rs[id], mid + 1, r);
// merge ls[id] and rs[id]
return id;
}
线段树合并
现在有两棵动态开点线段树,希望将两棵线段树的信息合并。
对于只有一棵线段树有信息的位置,将有信息的节点返回即可。对于两棵树均有信息的节点,递归合并两棵子树,再将两棵子树的信息合并。
假设有
int merge(int u, int v, int l, int r) {
if (!u || !v) return u | v;
if (l == r) {
// merge information u and v to u
return u;
}
int mid = (l + r) >> 1;
ls[u] = merge(ls[u], ls[v], l, mid);
rs[u] = merge(rs[u], rs[v], mid + 1, r);
// merge ls[u] and rs[u]
return u;
}
P4556 雨天的尾巴
Link
给定一棵
先随意定一个根。修改可以差分成:在
暴力的想法是每一个位置开一个桶,记录每一种类型出现次数。这样无论是查询还是合并,复杂度都不可承受。
可以将桶换成动态开点的权值线段树,使用线段树合并进行维护。