数据结构 Week 2 --- 平衡树
平衡树的算法很多,可分为有旋和无旋
无旋平衡树有:替罪羊树,fnq treap
有旋平衡树有:AVL树,Splay
还有很多其他的平衡树算法,不必掌握太多
主要掌握Splay和fhq treap,因为这两种可以支持区间操作
平衡树模板之Splay
核心思想:通过旋转使操作节点伸展到根节点
本来只要通过一两次旋转就能达到平衡了(AVL树),但是Splay应用了更多的旋转,使操作节点伸展到根节点
只通过单旋,操作节虽然能伸展到根节点,但是不能平衡高度
所以要通过双旋,使高度平衡
code:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<random>
#include<vector>
using namespace std;
const int MAXN = 1e6 + 1e5 + 7;
struct NODE {
int fa, son[2];
int val, siz;
int cnt;
}spl[MAXN];
int cnt = 0, root = 0;
inline bool is_y(int x, int f) { return spl[f].son[1] == x; }
void inline con(int x, int fa, int k) {
spl[x].fa = fa;
spl[fa].son[k] = x;
}
inline void update(int x) {
spl[x].siz = spl[spl[x].son[0]].siz + spl[spl[x].son[1]].siz + spl[x].cnt;
}
inline void newnode(int &x, int fa, int val) {
spl[x = ++cnt].siz = 1;
spl[x].cnt = 1;
spl[x].fa = fa;
spl[x].val = val;
}
void rotate(int x,int &fa) {
int ff = spl[fa].fa;
int k = is_y(x, fa);
int kk = is_y(fa, ff);
con(spl[x].son[k ^ 1], fa, k);
con(fa, x, k ^ 1);
fa = x;
con(fa, ff, kk);
update(spl[fa].son[k ^ 1]), update(fa);
}
void splaying(int x, int& y) {
while (x != y) {
int f = spl[x].fa, ff = spl[f].fa;
if (f == y) rotate(x, y);
else if (ff == y) {
is_y(x, f) == is_y(f, ff) ? rotate(f, y) : rotate(x, f);
rotate(x, y);
}
else {
is_y(x, f) == is_y(f, ff) ? rotate(f, ff) : rotate(x, f);
rotate(x, ff);
}
}
}
void delnode(int x) {
splaying(x, root);
if (spl[x].cnt > 1) spl[x].cnt--;
else if (spl[root].son[1]) {
int p = spl[root].son[1];
while (spl[p].son[0]) p = spl[p].son[0];
splaying(p, spl[root].son[1]);
con(spl[root].son[0], p, 0);
root = p;
spl[p].fa = 0;
update(root);
}
else root = spl[x].son[0], spl[root].fa = 0;
}
void ins(int& now, int fa, int val) {
if (!now) newnode(now, fa, val), splaying(now, root);
else if (val < spl[now].val) ins(spl[now].son[0], now, val);
else if (val > spl[now].val) ins(spl[now].son[1], now, val);
else spl[now].cnt++, spl[now].siz++, splaying(now, root);
}
void del(int now, int val) {
if (val == spl[now].val) delnode(now);
else if (val < spl[now].val) del(spl[now].son[0], val);
else del(spl[now].son[1], val);
}
int getrank(int val) {
int now = root, rank = 1;
while (now) {
if (val == spl[now].val) {
rank += spl[spl[now].son[0]].siz;
splaying(now, root);
break;
}
if (val < spl[now].val) now = spl[now].son[0];
else {
rank += spl[spl[now].son[0]].siz + spl[now].cnt;
now = spl[now].son[1];
}
}
return rank;
}
int getnum(int rank) {
int now = root;
while (now) {
int lsize = spl[spl[now].son[0]].siz;
if (lsize + 1 <= rank && rank <= lsize + spl[now].cnt) {
splaying(now, root);
break;
}
if (rank <= lsize) now = spl[now].son[0];
else {
rank -= lsize + spl[now].cnt;
now = spl[now].son[1];
}
}
return spl[now].val;
}
平衡树模板之fhq treap
核心思想:Treap + 随机化,Treap可以实现按某一个值分裂成两个子树,然后再合并
二叉搜索树对所有子树满足:左子树的所有值小于根节点,右子树的所有值大于根节点
堆对所有子树满足:左子树和右子树都大于/小于根节点
显然一棵树不能同时满足这两个性质,如果每个节点只有一个值的话
但是如果每个节点有两个值,那么就能满足其中一个值满足二叉搜索树的性质,另一个值满足堆的性质了
这样把二叉搜索树和堆叠在一起,便是Treap
fhq Treap就是把本来每个节点只有一个值的二叉搜索树,再新增一个随机的第二个值,然后使第二个值满足堆的性质
这样随机化的操作,相当于随机的插入顺序,使得二叉搜索树大概率平衡
核心操作是按某个值分裂成两个子树,实际上就是把一些边改一改,有些边删掉,有些边加到哪里
code:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<random>
using namespace std;
const int MAXN = 1e5 + 7;
struct NODE {
int l, r;
int val, key;
int size;
}fhq[MAXN];
int cnt = 0, root = 0;
std::mt19937 rnd(233);
inline int newnode(int val) {
fhq[++cnt].val = val;
fhq[cnt].key = rnd();
fhq[cnt].size = 1;
return cnt;
}
inline void update(int now) {
fhq[now].size = fhq[fhq[now].l].size + fhq[fhq[now].r].size + 1;
}
void split(int now, int val, int& x, int& y) {
if (!now) x = y = 0;
else {
if (fhq[now].val <= val) {
x = now;
split(fhq[now].r, val, fhq[now].r, y);
}
else {
y = now;
split(fhq[now].l, val, x, fhq[now].l);
}
update(now);
}
}
int merge(int x, int y) {
if (!x || !y)return x + y;
if (fhq[x].key > fhq[y].key) {
fhq[x].r = merge(fhq[x].r, y);
update(x);
return x;
}
else {
fhq[y].l = merge(x, fhq[y].l);
update(y);
return y;
}
}
int x, y, z;
inline void ins(int val) {
split(root, val, x, y);
root = merge(merge(x, newnode(val)), y);
}
inline void del(int val) {
split(root, val, x, z);
split(x, val - 1, x, y);
y = merge(fhq[y].l, fhq[y].r);
root = merge(merge(x, y), z);
}
inline int getrank(int val) {
split(root, val - 1, x, y);
int ans = fhq[x].size + 1;
root = merge(x, y);
return ans;
}
inline int getnum(int rank) {
int now = root;
while (now) {
if (fhq[fhq[now].l].size + 1 == rank) break;
else if (fhq[fhq[now].l].size >= rank) now = fhq[now].l;
else {
rank -= fhq[fhq[now].l].size + 1;
now = fhq[now].r;
}
}
return fhq[now].val;
}
inline int pre(int val) {
split(root, val - 1, x, y);
int now = x;
while (fhq[now].r) now = fhq[now].r;
root = merge(x, y);
return fhq[now].val;
}
inline int nxt(int val) {
split(root, val, x, y);
int now = y;
while (fhq[now].l) now = fhq[now].l;
root = merge(x, y);
return fhq[now].val;
}
文艺平衡树---平衡树实现区间反转
要把一段区间拎出来然后反转
怎么用平衡树实现?
一颗二叉搜索树,它的中序遍历的输出结果,一定是按权值的大小从小到大排好序的,不管这颗二叉搜索树是长怎么样的
如果要让它的一段区间反转,也就是这段区间要实现按权值从大到小的遍历
怎么做?
我们一开始先对初始数组从左往右给所有数一个次序值,按次序值从小到大输出就是初始的数组了
那么反转一段区间,就是把这段区间的所有次序值反过来
暴力O(n)的反转次序值肯定是不行的
所以就用懒标记
一个点上面有懒标记,就代表以这个点为根节点的子二叉搜索树,它的次序值是全部反转了的
假设我打了一个懒标记,再进行输出的时候,没打懒标记的点就正常遍历,打了懒标记的点就先下传懒标记,再正常遍历
要反转一段区间,可以这样做:在这段区间里选一个点,点的左边部分要反转,右边部分要反转,然后把点的左右两边互换
所以下传懒标记的操作,就是把懒标记下传到左右儿子,然后交换左右儿子
就可以神奇的实现这个O(log)的反转操作
code:
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int MAXN = 1e5 + 7;
struct NODE {
int fa, son[2];
int val, siz;
int cnt;
int lazy;
}spl[MAXN];
int cnt = 0, root = 0;
int n, m, l, r;
inline bool is_y(int x, int f) { return spl[f].son[1] == x; }
void inline con(int x, int fa, int k) {
spl[x].fa = fa;
spl[fa].son[k] = x;
}
inline void update(int x) {
spl[x].siz = spl[spl[x].son[0]].siz + spl[spl[x].son[1]].siz + spl[x].cnt;
}
inline void newnode(int& x, int fa, int val) {
spl[x = ++cnt].siz = 1;
spl[x].cnt = 1;
spl[x].fa = fa;
spl[x].val = val;
}
void pd(int now) {
if (spl[now].lazy) {
spl[spl[now].son[0]].lazy ^= 1;
spl[spl[now].son[1]].lazy ^= 1;
swap(spl[now].son[0], spl[now].son[1]);
spl[now].lazy = 0;
}
}
void rotate(int x, int& fa) {
int ff = spl[fa].fa;
int k = is_y(x, fa);
int kk = is_y(fa, ff);
con(spl[x].son[k ^ 1], fa, k);
con(fa, x, k ^ 1);
fa = x;
con(fa, ff, kk);
update(spl[fa].son[k ^ 1]), update(fa);
}
void splaying(int x, int& y) {
while (x != y) {
int f = spl[x].fa, ff = spl[f].fa;
if (f == y) rotate(x, y);
else if (ff == y) {
is_y(x, f) == is_y(f, ff) ? rotate(f, y) : rotate(x, f);
rotate(x, y);
}
else {
is_y(x, f) == is_y(f, ff) ? rotate(f, ff) : rotate(x, f);
rotate(x, ff);
}
}
}
void ins(int& now, int fa, int val) {
if (!now) newnode(now, fa, val), splaying(now, root);
else if (val < spl[now].val) ins(spl[now].son[0], now, val);
else if (val > spl[now].val) ins(spl[now].son[1], now, val);
else spl[now].cnt++, spl[now].siz++, splaying(now, root);
}
int getnode(int rank) {//找到排名为rank的点的编号
int now = root;
while (now) {
pd(now);
int lsize = spl[spl[now].son[0]].siz;
if (lsize + 1 <= rank && rank <= lsize + spl[now].cnt) {
splaying(now, root);
break;
}
if (rank <= lsize) now = spl[now].son[0];
else {
rank -= lsize + spl[now].cnt;
now = spl[now].son[1];
}
}
return now;
}
void work(int l, int r) {
int nl = getnode(l), nr = getnode(r + 2);
splaying(nl, root);
splaying(nr, spl[nl].son[1]);
spl[spl[nr].son[0]].lazy ^= 1;
}
void print(int now) {
if (!now) return;
pd(now);
if (spl[now].son[0]) print(spl[now].son[0]);
if (spl[now].val > 0 && spl[now].val < n + 1) printf("%d ", spl[now].val);
if (spl[now].son[1]) print(spl[now].son[1]);
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n + 2; i++) {//1是0号,2到n+1是1到n号,n+2是n+1号
ins(root, 0, i - 1);
}
while (m--) {
scanf("%d%d", &l, &r);
work(l, r);
}
print(root);
printf("\n");
return 0;
}
二逼平衡树之线段树套平衡树
当我学了动态主席树(其实不是主席树了,而是树状数组套权值线段树,或者线段树套权值线段树也行)后,我做到一个数据结构题(CCPC长春B题)的时候很高兴地用了这个模板,然后惨MLE了几十发QAQ
一直以来很少考虑过空间问题
总是以为开几十个线段树都没什么大不了的。。
ICPC昆明M题我开了60个long long的线段树,没想到动态开点的写法爆了1G的内存。。从来没想过MLE的问题
经历了这两题,发现深入数据结构题后,需要考虑省空间这个问题了
这就一下子变难了很多,不仅要考虑时间,也要仔细考虑空间了
树状数组套权值线段树,其空间复杂度是O(nlognlogn),大多时候查询的时间复杂度是O(nlognlogn)
线段树套平衡树,其空间复杂度是O(nlogn),大多时候查询的时间复杂度是O(nlognlogn)
可以发现,第二种套法比第一种套法优一个log的空间复杂度
对于本题(二逼平衡树)而言,第二种套法的时间复杂度为O(nlognlognlogn),比第一种套法的时间复杂度多了一个log
两种套法都能AC
code:(线段树套平衡树)
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int MAXN = 1e5 + 7;
const int INF = 1e9 + 7;
struct NODE {
int l, r, mid;
int root;
}tree[MAXN * 4];
int a[MAXN];
///////////////////////////
//平衡树
struct SPL {
int fa, son[2];
int val, siz, cnt;
}spl[MAXN * 40];
int n, m, op, l, r, pos, k;
int cnt = 0;
inline bool is_y(int x, int fa) { return spl[fa].son[1] == x; }
inline void con(int x, int fa, int k) {
spl[x].fa = fa;
spl[fa].son[k] = x;
}
inline void newnode(int& now, int fa, int val) {
spl[now = ++cnt].cnt = 1;
spl[now].siz = 1;
spl[now].val = val;
spl[now].fa = fa;
}
inline void update(int now) {
spl[now].siz = spl[spl[now].son[0]].siz + spl[spl[now].son[1]].siz + spl[now].cnt;
}
inline void rotate(int x, int& y) {
int ff = spl[y].fa;
int k = is_y(x, y), kk = is_y(y, ff);
con(spl[x].son[k ^ 1], y, k);
con(y, x, k ^ 1);
y = x;
con(y, ff, kk);
update(spl[y].son[k ^ 1]); update(y);
}
void splaying(int x, int& y) {
while (x != y) {
int f = spl[x].fa, ff = spl[f].fa;
if (f == y) rotate(x, y);
else if (ff == y) {
is_y(x, f) == is_y(f, y) ? rotate(f, y) : rotate(x, f);
rotate(x, y);
}
else {
is_y(x, f) == is_y(f, ff) ? rotate(f, ff) : rotate(x, f);
rotate(x, ff);
}
}
}
void delnode(int now, int& root) {
splaying(now, root);
if (spl[now].cnt > 1) spl[now].cnt--, spl[now].siz--;
else if (spl[root].son[1]) {
int p = spl[root].son[1];
while (spl[p].son[0]) p = spl[p].son[0];
splaying(p, spl[root].son[1]);
con(spl[root].son[0], p, 0);
spl[p].fa = 0;
root = p;
update(root);
}
else root = spl[now].son[0], spl[root].fa = 0;
}
void ins(int& now, int& root, int fa, int val) {
if (!now) newnode(now, fa, val), splaying(now, root);
else if (val < spl[now].val) ins(spl[now].son[0], root, now, val);
else if (val > spl[now].val) ins(spl[now].son[1], root, now, val);
else spl[now].cnt++, spl[now].siz++, splaying(now, root);
}
void del(int now, int& root, int val) {
if (spl[now].val == val) delnode(now, root);
else if (val < spl[now].val) del(spl[now].son[0], root, val);
else del(spl[now].son[1], root, val);
}
int getrank(int& root, int val) {
int now = root, rank = 1;
while (now) {
if (val == spl[now].val) {
rank += spl[spl[now].son[0]].siz;
splaying(now, root);
break;
}
if (val < spl[now].val)now = spl[now].son[0];
else {
rank += spl[spl[now].son[0]].siz + spl[now].cnt;
now = spl[now].son[1];
}
}
return rank;
}
int getnum(int& root, int rank) {
int now = root;
while (now) {
int lsize = spl[spl[now].son[0]].siz;
if (lsize + 1 <= rank && rank <= lsize + spl[now].cnt) {
splaying(now, root);
break;
}
if (rank <= lsize) now = spl[now].son[0];
else {
rank -= lsize + spl[now].cnt;
now = spl[now].son[1];
}
}
return spl[now].val;
}
int prenum(int& root, int val) {
return getnum(root, getrank(root, val) - 1);
}
int nxtnum(int& root, int val) {
return getnum(root, getrank(root, val + 1));
}
///////////////
void build(int pos, int l, int r) {//建树建根
tree[pos].l = l; tree[pos].r = r; tree[pos].mid = l + r >> 1;
tree[pos].root = ++cnt;
if (l == r) return;
int mid = l + r >> 1;
build(pos << 1, l, mid);
build(pos << 1 | 1, mid + 1, r);
}
void SEG_ins(int pos, int p, int val) {//插入
ins(tree[pos].root, tree[pos].root, 0, val);
if (tree[pos].l == tree[pos].r) return;
int mid = tree[pos].mid;
if (p <= mid) SEG_ins(pos << 1, p, val);
else SEG_ins(pos << 1 | 1, p, val);
}
void SEG_del(int pos, int p, int val) {//删除
del(tree[pos].root, tree[pos].root, val);
if (tree[pos].l == tree[pos].r) return;
int mid = tree[pos].mid;
if (p <= mid) SEG_del(pos << 1, p, val);
else SEG_del(pos << 1 | 1, p, val);
}
int SEG_rank(int pos, int l, int r, int val) {//比val小的数的个数
if (tree[pos].l == l && tree[pos].r == r) {
return getrank(tree[pos].root, val) - 1;
}
int mid = tree[pos].mid;
if (r <= mid) return SEG_rank(pos << 1, l, r, val);
else if (l > mid) return SEG_rank(pos << 1 | 1, l, r, val);
else return SEG_rank(pos << 1, l, mid, val) + SEG_rank(pos << 1 | 1, mid + 1, r, val);
}
int SEG_pre(int pos, int l, int r, int val) {
if (tree[pos].l == l && tree[pos].r == r) {
return prenum(tree[pos].root, val);
}
int mid = tree[pos].mid;
if (r <= mid) return SEG_pre(pos << 1, l, r, val);
else if (l > mid) return SEG_pre(pos << 1 | 1, l, r, val);
else return max(SEG_pre(pos << 1, l, mid, val), SEG_pre(pos << 1 | 1, mid + 1, r, val));
}
int SEG_nxt(int pos, int l, int r, int val) {
if (tree[pos].l == l && tree[pos].r == r) {
int res = nxtnum(tree[pos].root, val);
if (!res) res = INF;
return res;
}
int mid = tree[pos].mid;
if (r <= mid) return SEG_nxt(pos << 1, l, r, val);
else if (l > mid) return SEG_nxt(pos << 1 | 1, l, r, val);
else return min(SEG_nxt(pos << 1, l, mid, val), SEG_nxt(pos << 1 | 1, mid + 1, r, val));
}
int main()
{
cin >> n >> m;
build(1, 1, n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]), SEG_ins(1, i, a[i]);
while (m--) {
scanf("%d", &op);
if (op == 3) {
scanf("%d%d", &pos, &k);
SEG_del(1, pos, a[pos]);
a[pos] = k;
SEG_ins(1, pos, a[pos]);
}
else {
scanf("%d%d%d", &l, &r, &k);
if (op == 1) printf("%d\n", SEG_rank(1, l, r, k) + 1);
if (op == 2) {
int al = 0, ar = INF;
while (al < ar) {//找到排名大于k的最小的数
int mid = al + ar >> 1;
int rk = SEG_rank(1, l, r, mid) + 1;
if (rk < k + 1) al = mid + 1;
else ar = mid;
}
printf("%d\n", SEG_pre(1, l, r, al));
}
if (op == 4) {
int val = SEG_pre(1, l, r, k);
if (val == 0 || val == k) printf("-2147483647\n");
else printf("%d\n", val);
}
if (op == 5) {
int val = SEG_nxt(1, l, r, k);
if (val == 0 || val == k || val == INF) printf("2147483647\n");
else printf("%d\n", val);
}
}
}
return 0;
}
可持久化平衡树
这个就需要用fhq treap了
待补。。。

浙公网安备 33010602011771号