数据结构 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了

待补。。。

 

posted @ 2021-05-29 14:41  beta_dust  阅读(54)  评论(0编辑  收藏  举报