首页 > 其他分享 >【学习笔记】(13) 平衡树——记住不的板子

【学习笔记】(13) 平衡树——记住不的板子

时间:2023-10-06 22:45:52浏览次数:38  
标签:rt 13 ch int 笔记 板子 merge split 权值

Treap

Splay

无旋Treap——fhq treap

简介

就是没有旋转操作的 Treap,一些性质什么的都跟 Treap 类似。

算法介绍

(1)merge(x,y)

将两棵“有序”(x中元素的权值最大值小于 y 中元素权值最小值)的Treap合并成一棵。


int ch[N][2], sz[N], pri[N], val[N];
//val 为权值,pri 为优先级,sz 为子树大小 
int merge(int x, int y){
		if(!x || !y) return x + y;
		//若 x 的优先级小,则它应该新树的根 
		//由于 x 中元素都小于 y 中元素权值,所以合并 x 的右子树和 y 
		if(pri[x] < pri[y]) return ch[x][1] = merge(ch[x][1], y), modify(x), x;
		//另一种情况 
		else return ch[y][0] = merge(x, ch[y][0]), modify(y), y;
	} 

(2.1) Split(u, v, x, y)

根据权值 v 将 u 这棵 Treap 分为两棵平衡树 x ,y,其中 x 中元素权值均小于等于 v, y 中元素权值均大于 v。

void split(int u, int v, int &x, int &y){
	if(!u) return x = y = 0, void();
	if(val[u] <= v) x = u, split(ch[u][1], v, ch[u][1], y); //u 的权值比 v 小,说明 u 以及 u 的左子树都属于 x, 所以递归划分 u 的右子树  
	else y = u, split(ch[u][0], v, x, ch[u][0]); //另一种情况 
	modify(u);
}

(2.2) Split(u, kth, x, y)

跟据子树大小将树 u 分成两棵树 x, y。 x 的大小为 k ,他包含树 u 中权值前 k 小的元素。

void split(int u, int k, int &x, int &y){
	if(!u) return x = y = 0, void();
	if(k <= sz[ch[u][0]]) y = u, split(ch[u][0], k, x, ch[u][0]); //左子树大小大于 k ,说明 u 以及 u 的右子树都在 y 中, 递归划分左子树 
	else x = u, split(ch[u][1], k - sz[ch[u][0]] - 1, ch[u][1], y);
	modify(u);
}

(3) insert

int newnode(int v){ // 新建一个权值为 v 的节点 
	sz[++tot] = 1, pri[tot] = rnd(), val[tot] = v;
	return tot;
}
void insert(int k, int v){ //在以 rt 为根的 Treap 中 k 的位置插入一个值为 v 的元素 
	split(rt, k, r1, r2); //根据排名分 
	rt = merge(r1, merge(newnode(v), r2));
}
void insert(int &u, int v){ //在以 u 为根的 Treap 中插入一个值为 v 的元素
	split(u, v, r1, r2); //根据权值分 
	u = merge(r1, merge(newnode(v), r2));
}
//上面两个 split 是不一样的 

(4) delte(u,v)

删除 u 为根的 Treap 中权值为 v 的所有元素。

void del(int &u, int v){
	int L, R, p, q; 
	split(u, v, L, R); //调用 (2.1) 对应代码 
	split(L, v - 1, p, q);
	u = merge(L, merge(p, q));
} 

注意:此操作会将所有权值为 v 的元素都删除掉,若只删除一个,则需要求出 v 的排名,在调用 split(u,k,x,y) 以便实现删除一个元素。

(5) modify(u,l,r)

对区间 \([l,r]\) 进行操作,例如区间加、区间翻转等。

void modify(int &u, int l, int r){
	int L, R, p, q; 
	split(u, l - 1, L, R); //调用 (2.2) 对应代码 
	split(R, r - l + 1, p, q);
	solve(p);
	u = merge(L, merge(p, q));
} 

Merge(x, y)

合并两个 “乱序” Treap (即 \(x\) 中的元素并不全小于 \(y\) 中的元素),也就是合并任意两个 Treap。
复杂度 \(O(n\log & n)\)

void Merge(int x, int y){
	if(!x || !y) return x + y;
	if(pri[x] > pri[y]) swap(x, y);
	int L, R;
	split(v, val[u], L, R); //调用 (2.1) 对应代码 
	ch[x][0] = Merge(ch[x][0], L);
	ch[x][1] = Merge(ch[x][1], R);
}

其他操作

1.删除

删除一个数 \(v\):先用 \(v\) 按照权值分裂成两棵树 \(x,z\),再用 \(v−1\) 按照权值将 \(x\) 分裂成两棵树 \(x,y\)。此时 \(y\) 的所有节点的权值就都是 \(v\)。但是我们只能删一个,因此可以将 \(y\) 的左右节点合并起来,这样 \(y\) 的节点个数就少了一个。最后再合并回来即可。

void del(int v){
	int x, y, z;
	split(rt, v, x, z), split(x, v - 1, x, y);
	y = merge(ch[y][0], ch[y][1]);
	rt = merge(merge(x, y), z); 
}

2.第 \(k\) 大

查找第 \(k\) 大的数:假设当前遍历到节点 \(u\)。若 \(k≤sz_{ls_u}\) 则向左儿子递归;若 \(k=sz_{ls_u}+1\) 则直接返回 \(val_u\);否则向右儿子递归。

int kth(int u, int k){
	while(1){
		if(k <= sz[ch[u][0]]) u = ch[u][0];
		else if(k == sz[ch[u][0]] + 1) return u;
		else k -= sz[ch[u][0]] + 1, u = ch[u][1]; 
	}
}

3. 前缀/后继

查找 \(v\) 的前驱:用 \(v−1\) 按照权值分裂成 \(x,y\),再找 \(x\) 的最大值即可(即 \(x\) 的第 \(sz_x\) 大值)。后继同理。

//前驱 
split(rt, v - 1, x, y);
printf("%d\n", val[kth(x, sz[x])]);
rt = merge(x, y);

//后继 
split(rt, v, x, y);
printf("%d\n", val[tr.kth(y, 1)]);
rt = tr.merge(x, y);

4. 排名

查询 \(v\) 的排名:用 \(v−1\) 按照权值分裂成 \(x,T\),那么答案即为 \(sz_x+1\)。最后合并。

split(rt, v - 1, x, y);
printf("%d\n", sz[x] + 1);
rt = merge(x, y);

例题

Ⅰ. P3369 【模板】普通平衡树

模板,操作上面都讲过。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 67;
int read(){
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
	while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
	return x * f;
}

bool _u;

mt19937 rnd((unsigned)time(0));
int n, tot, rt;
int ch[N][2], sz[N], pri[N], val[N];

struct FHQ{
	void modify(int x){sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + 1;}
	int new_node(int v){sz[++tot] = 1, val[tot] = v, pri[tot] = rnd(); return tot;}
	int merge(int x, int y){
		if(!x || !y) return x + y;
		if(pri[x] < pri[y]) return ch[x][1] = merge(ch[x][1], y), modify(x), x;
		else return ch[y][0] = merge(x, ch[y][0]), modify(y), y;
	} 
	void split(int u, int v, int &x, int &y){
		if(!u) return x = y = 0, void();
		if(val[u] <= v) x = u, split(ch[u][1], v, ch[u][1], y);
		else y = u, split(ch[u][0], v, x, ch[u][0]);
		modify(u);
	}
	int kth(int u, int k){
		while(1){
			if(k <= sz[ch[u][0]]) u = ch[u][0];
			else if(k == sz[ch[u][0]] + 1) return u;
			else k -= sz[ch[u][0]] + 1, u = ch[u][1]; 
		}
	}
}tr;

bool _v;

int main(){
	cerr << abs(&_u - &_v) / 1048576.0 << " MB\n";

	n = read();
	while(n--){
		int opt = read(), v = read(), x, y, z;
		if(opt == 1){
			tr.split(rt, v, x, y);
			rt = tr.merge(tr.merge(x, tr.new_node(v)), y);
		} else if(opt == 2){
			tr.split(rt, v, x, z), tr.split(x, v - 1, x, y);
			y = tr.merge(ch[y][0], ch[y][1]);
			rt = tr.merge(tr.merge(x, y), z); 
		} else if(opt == 3){
			tr.split(rt, v - 1, x, y);
			printf("%d\n", sz[x] + 1);
			rt = tr.merge(x, y);
		} else if(opt == 4){
			printf("%d\n", val[tr.kth(rt, v)]);
		} else if(opt == 5){
			tr.split(rt, v - 1, x, y);
			printf("%d\n", val[tr.kth(x, sz[x])]);
			rt = tr.merge(x, y);
		} else{
			tr.split(rt, v, x, y);
			printf("%d\n", val[tr.kth(y, 1)]);
			rt = tr.merge(x, y);
		}
	}
	return 0;
} 

Ⅱ. P4146 序列终结者

为了写这篇博客,终于把6个月前没过的代码调出来了。

对于区间反转与区间加,直接打标记就行了。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 51;
int read(){
    int x = 0, f = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
    return x * f;   
}
mt19937 rnd((unsigned)time(0));
int n, m, rt, r1, r2, tot;
int maxn[N], val[N], add[N], sz[N];
int ch[N][2], pri[N];
bool lazy[N];
int newnode(int v){
    sz[++tot] = 1, pri[tot] = rnd(), val[tot] = v;
    maxn[tot] = v, add[tot] = 0, lazy[tot] = 0;
    return tot;
}
void pushup(int u){
    sz[u] = sz[ch[u][1]] + sz[ch[u][0]] + 1;
    maxn[u] = val[u];
    if(ch[u][1]) maxn[u] = max(maxn[u], maxn[ch[u][1]]);
    if(ch[u][0]) maxn[u] = max(maxn[u], maxn[ch[u][0]]);
}
void pushdown(int x){
    if(lazy[x]){
        lazy[ch[x][1]] ^= 1, lazy[ch[x][0]] ^= 1;
        swap(ch[x][1], ch[x][0]);
        lazy[x] = 0;
    }
    if(add[x]){
        add[ch[x][1]] += add[x], add[ch[x][0]] += add[x];
        val[ch[x][1]] += add[x], val[ch[x][0]] += add[x];
        maxn[ch[x][1]] += add[x], maxn[ch[x][0]] += add[x];
        add[x] = 0;
    }
}
int merge(int x, int y){
    pushdown(x), pushdown(y);
    if(!x || !y) return x + y;
    int u;
    if(pri[x] < pri[y]) u = x, ch[x][1] = merge(ch[x][1], y);
    else u = y, ch[y][0] = merge(x, ch[y][0]);
    pushup(u);
    return u;
}
void split(int u, int k, int &x, int &y){
    if(!u) return x = y = 0, void();
    pushdown(u);
    if(sz[ch[u][0]] >= k) y = u, split(ch[u][0], k, x, ch[u][0]);
    else x = u, split(ch[u][1], k - sz[ch[u][0]] - 1, ch[u][1], y);
    pushup(u);
}
void insert(int k, int v){
    split(rt, k, r1, r2), rt = merge(r1, merge(newnode(v), r2));
}
int main(){
    n = read(), m = read();
    for(int i = 1; i <= n; ++i) insert(i - 1, 0);
    while(m--){
    	int opt = read(), l = read(), r = read(), v;
    	int x, y, z;
        if(opt == 1){
            v = read();
            split(rt, l - 1, x, y);
            split(y, r - l + 1, y, z);
            val[y] += v, maxn[y] += v, add[y] += v;
            rt = merge(x, merge(y, z));
        }else if(opt == 2){
            split(rt, l - 1, x, y);
            split(y, r - l + 1, y, z);
            lazy[y] ^= 1;
            rt = merge(x, merge(y, z));
        }else{
            split(rt, l - 1, x, y);
            split(y, r - l + 1, y, z);
            printf("%d\n", maxn[y]);
            rt = merge(x, merge(y, z));
        }
    }
    return 0;
}

重量平衡树

标签:rt,13,ch,int,笔记,板子,merge,split,权值
From: https://www.cnblogs.com/jiangchen4122/p/17417676.html

相关文章

  • 虚树 学习笔记
    2023/10/6发现找不到题做了,决定学习新算法。经过在一些题单中的翻找,决定学习虚树。Part1.引入以一道例题来引入虚树吧。[HEOI2014]大工程给定一棵有\(n\)个点的树,边权均为\(1\)。现在有\(q\)次询问。每次询问取\(k\)个点出来建立完全图。定义连接两个点的代价为......
  • 1324234432
    eyJ2ZXJzaW9uIjoxLjMsImZlYXR1cmVzIjp7ImxvY2F0aW9uIjp7InJvb20iOnRydWUsIm91dHNpZGUiOnRydWUsIndvcmxkIjp0cnVlfX0sInN0b3JlcyI6eyJ3b29kIjoxMDc4LCJmdXIiOjIxOS41LCJiYWl0IjoyMzEsIm1lYXQiOjc0LjUsInRlZXRoIjoxMTQsInNjYWxlcyI6NTEsImNsb3RoIjoyOSwiY29tcGFzcyI6MSwiY3VyZWQ......
  • 国庆笔记
    1、 快的保护慢的:比如使用guava保护redis,使用redis保护mysql。人多力量大(集群):一个Mysql不行,就分库分表;一个redis不行,就redis集群;主不行,从可以帮忙扛读流量;尽可能懒:能一会做,就别现在做,能异步就别同步;比如读集群通过异步推送数据,能接受一定时延,就不同步。 https://ju......
  • 【TinyWebServer】13踩坑和面试题
    踩坑在此项目中遇到的一些比较有意义的问题大文件传输先看下游双书上发送逻辑这块的代码,发送数据只调用了writev函数,并对其返回值是否异常做了处理。boolhttp_conn::write(){ inttemp=0; intbyte_have_send=0; intbyte_to_send=m_write_idx; if(byte_to_......
  • 39-13
    假设有两个按元素值递减次序排列的线性表,均以单链表的形式存储,编写算法,将这两个单链表合成一个按值递减的单链表,使用原链表的结点。没啥好说的,这个有手就行#include<stdio.h>#include<stdlib.h>typedefstructnode{intdata;structnode*next;}LNode,*LinkLi......
  • RK3588开发笔记(一):基于方案商提供的宿主机交叉编译Qt5.12.10
    前言  rk3588开发车机,方案上提供的宿主机只是编译rksdk的版本,并未编译好Qt,那么需要自行交叉编译Qt系统。选择的Qt的版本为5.12.10。 宿主机准备  下载并打开宿主机,只有sdk,并没有交叉编译的Qt。   Qt准备  下载Qt5.12.10的开源软件(方案商提供)。  ......
  • openGauss学习笔记-91 openGauss 数据库管理-内存优化表MOT管理-内存表特性-使用MOT-M
    openGauss学习笔记-91openGauss数据库管理-内存优化表MOT管理-内存表特性-使用MOT-MOT使用MOT外部支持工具为了支持MOT,修改了以下外部openGauss工具。请确保使用的工具是最新版本。下面将介绍与MOT相关的用法。有关这些工具及其使用方法的完整说明,请参阅《工具与命令参考》。91......
  • U9C学习笔记
    建立物料清单BOM时,必须钩选“主批量“,否则建好之后重新再打开窗体,建好的树型BOM会断层。建立完之后,必须每一层物料都全部审核,否则MPS计算时无法展开多阶物料。  MPS计算完成之后,在”计划者工作台“可以查看到”结束净算“,说明已计算完成。 MPS计算时查看错误日志。......
  • CF131D Subway 题解
    题目传送门前置知识强连通分量|最短路解法考虑用Tarjan进行缩点,然后跑最短路。缩点:本题的缩点有些特殊,基于有向图缩点修改而得,因为是无向图,所以在Tarjan过程中要额外记录一下从何处转移过来,防止在同一处一直循环。基环树上找环还有其他方法,这里仅讲解使用Tarjan求......
  • 软件设计开发笔记6:基于QT的Modbus RTU从站
      Modbus是一种常见的工业系统通讯协议。在我们的设计开发工作中经常使用到它。作为一种主从协议,在上一篇我们实现了MobusRTU主站工具,接下来这一篇中我们将简单实现一个基于QT的MobusRTU从站工具。1、概述  ModbusRTU从站应用很常见,有一些是通用的,有一些是专用的。而这里......