Splay树 模板题入门

2024-04-13 03:08
文章标签 模板 入门 splay

本文主要是介绍Splay树 模板题入门,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

模板题①:HDU 4453 Looploop

以下代码摘自:https://blog.csdn.net/ck_boss/article/details/48031457

意识到这样的题原来才是模板题之后,我才知道Splay树中,翻转区间是很基本的功能。学到技巧:对区间[L,R]的翻转是用结点L-1和R+1作为一个边界,然后对其子节点标记翻转。很多Splay树的操作都利用了这个边界设置的技巧。
这里有区间增,区间翻转,单点增删,删除子树,求Kth小,splay的旋转结点,这些功能。

总的来说,核心还是Splay函数的旋转功能,即把一个结点变为另一个结点的子节点,或者之间变为根,围绕这一点展开了各种功能。

理解一点,树里面的大小关系就是左右关系,和实际包含的结点权值无关。

#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<iostream>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 220000;#define Key_value tree[tree[root][1]][0]int p[maxn], n, m, k1, k2;int tree[maxn][2], rev[maxn], add[maxn], sz[maxn], pre[maxn], key[maxn];
int root, tot1, tot2, s[maxn];void newnode(int& x, int fa, int v) {if (tot2)x = s[tot2--];else x = ++tot1;tree[x][0] = tree[x][1] = rev[x] = add[x] = 0;sz[x] = 1; pre[x] = fa; key[x] = v;
}
void del_tree(int x) {if (x) {s[++tot2] = x;del_tree(tree[x][0]);del_tree(tree[x][1]);}
}
void update_rev(int x) {if (!x)return;swap(tree[x][0], tree[x][1]);rev[x] ^= 1;
}
void update_add(int x, int d) {if (!x)return;key[x] += d; add[x] += d;
}
void pushup(int x) {sz[x] = sz[tree[x][0]] + sz[tree[x][1]] + 1;
}
void pushdown(int x) {if (rev[x]) {update_rev(tree[x][0]);update_rev(tree[x][1]);rev[x] = 0;}if (add[x]) {update_add(tree[x][0], add[x]);update_add(tree[x][1], add[x]);add[x] = 0;}
}
void Rotate(int x, int c) {int y = pre[x];pushdown(y);pushdown(x);tree[y][!c] = tree[x][c];pre[tree[x][c]] = y;if (pre[y])tree[pre[y]][tree[pre[y]][1] == y] = x;pre[x] = pre[y];tree[x][c] = y;pre[y] = x;pushup(y);
}
void splay(int x, int goal) {pushdown(x);while (pre[x] != goal) {if (pre[pre[x]] == goal) {//再转一次就完成了pushdown(pre[x]); pushdown(x);Rotate(x, tree[pre[x]][0] == x);}else {pushdown(pre[pre[x]]); pushdown(pre[x]); pushdown(x);int y = pre[x];int c = (tree[pre[y]][0] == y);if (tree[y][c] == x) {//三点不共线Rotate(x, !c);Rotate(x, c);}else {//三点共线Rotate(y, c);Rotate(x, c);}}}pushup(x);if (goal == 0)root = x;
}
int kth(int x, int k) {pushdown(x);int t = sz[tree[x][0]] + 1;if (k == t)return x;if (t < k)return kth(tree[x][1], k - t);else return kth(tree[x][0], k);
}
void ADD(int L, int R, int d) {splay(kth(root, L), 0);splay(kth(root, R + 2), root);update_add(Key_value, d);pushup(tree[root][1]);pushup(root);
}
void REVERSE(int L, int R) {splay(kth(root, L), 0);splay(kth(root, R + 2), root);update_rev(Key_value);pushup(tree[root][1]);pushup(root);
}
void DELETE(int k) {//删除k位置的结点splay(kth(root, k), 0);splay(kth(root, k + 2), root);del_tree(Key_value);pre[Key_value] = 0;Key_value = 0;pushup(tree[root][1]);pushup(root);
}
void INSERT(int k, int v) {//增加结点到k位置的右边splay(kth(root, k + 1), 0);splay(kth(root, k + 2), root);newnode(Key_value, tree[root][1], v);pushup(tree[root][1]);pushup(root);
}
void buildtree(int& x, int L, int R, int fa) {if (L > R)return;int mid = (L + R) >> 1;newnode(x, fa, p[mid]);buildtree(tree[x][0], L, mid - 1, x);buildtree(tree[x][1], mid + 1, R, x);pushup(x);
}
void init(int n) {root = tot1 = tot2 = 0;tree[root][0] = tree[root][1] = pre[root] = sz[root] = 0;newnode(root, 0, inf);//设置左右两个边界点,实际操作是在这两个点之间newnode(tree[root][1], root, inf);buildtree(Key_value, 1, n, tree[root][1]);pushup(tree[root][1]);pushdown(root);
}
int main(void) {string cmd; int kase = 0;while (~scanf("%d %d %d %d", &n, &m, &k1, &k2) && (n || m || k1 || k2)) {for (int i = 1; i <= n; i++)scanf("%d", &p[i]);init(n);printf("Case #%d:\n", ++kase);while (m--) {cin >> cmd;if (cmd == "query") {splay(kth(root, 1), 0);splay(kth(root, 3), root);printf("%d\n", key[Key_value]);}else if (cmd == "move") {int to;scanf("%d", &to);if (to == 1) {//位置左移,最右侧结点放到最左侧splay(kth(root, n), 0);splay(kth(root, n + 2), root);int tmp = key[Key_value];DELETE(n);//取出最右边结点INSERT(0, tmp);//放到最左侧}else {//与上面相反splay(kth(root, 1), 0);splay(kth(root, 3), root);int tmp = key[Key_value];DELETE(1);INSERT(n - 1, tmp);}}else if (cmd == "delete") {DELETE(1); n--;//删除当前指向的结点}else if (cmd == "insert") {int v; scanf("%d", &v);INSERT(1, v); n++;//增加结点到当前指向位置的右侧}else if (cmd == "reverse") {REVERSE(1, k1);}else if (cmd == "add") {int d; scanf("%d", &d);ADD(1, k2, d);}}}return 0;
}

…看到了Kuang_bin大神的模板,看来Key_value的命名是比较流行的了。

模板题②:HDU 1890 Robotic Sort
这里有删除根节点的写法,可供参考。

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 1e5 + 5;
int root;
int rev[maxn], pre[maxn], sz[maxn];
int tree[maxn][2];
struct node {int val, id;bool operator < (const node& A)const {if (val == A.val)return id < A.id;return val < A.val;}
}nodes[maxn];
void pushup(int x) {sz[x] = sz[tree[x][0]] + sz[tree[x][1]] + 1;
}
void update_rev(int x) {if (!x)return;swap(tree[x][0], tree[x][1]);rev[x] ^= 1;
}
void pushdown(int x) {if (rev[x]) {update_rev(tree[x][0]);update_rev(tree[x][1]);rev[x] = 0;}
}
void Rotate(int x, int c) {//c=1右旋,c=0左旋int y = pre[x];pushdown(y);pushdown(x);tree[y][!c] = tree[x][c];//把x的儿子送给y当儿子pre[tree[x][c]] = y;//让y的新儿子认y当父亲if (pre[y])tree[pre[y]][tree[pre[y]][1] == y] = x;//y的父亲收x当儿子,不要y了pre[x] = pre[y];//x认新父亲tree[x][c] = y;//y成了x的儿子pre[y] = x;//y认x当父亲pushup(y);pushup(x);
}
void splay(int x, int goal) {//把节点x旋转为goal的儿子,如果goal=0,则旋转到根pushdown(x);while (pre[x] != goal) {if (pre[pre[x]] == goal) {pushdown(pre[x]); pushdown(x);Rotate(x, tree[pre[x]][0] == x);}else {pushdown(pre[pre[x]]); pushdown(pre[x]); pushdown(x);int y = pre[x];int c = (tree[pre[y]][0] == y);if (tree[y][c] == x) {//c=1,y有右儿子是x,y是父亲的左儿子Rotate(x, !c);    //c=0,y的左儿子是x,y是父亲的右儿子Rotate(x, c);}else {Rotate(y, c);Rotate(x, c);}}}pushup(x);if (goal == 0)root = x;
}
int get_max(int x) {pushdown(x);while (tree[x][1]) {x = tree[x][1];pushdown(x);}return x;
}
void del_root() {//删除根节点if (tree[root][0] == 0) {root = tree[root][1];pre[root] = 0;}else {int m = get_max(tree[root][0]);splay(m, root);tree[m][1] = tree[root][1];pre[tree[root][1]] = m;root = m;pre[root] = 0;pushup(root);}
}
void newnode(int& x, int fa, int val) {x = val;pre[x] = fa;sz[x] = 1;rev[x] = 0;tree[x][0] = tree[x][1] = 0;
}
void buildtree(int& x, int l, int r, int fa) {if (l > r)return;int mid = (l + r) >> 1;newnode(x, fa, mid);buildtree(tree[x][0], l, mid - 1, x);buildtree(tree[x][1], mid + 1, r, x);pushup(x);
}
void init(int n) {root = 0;tree[root][0] = tree[root][1] = pre[root] = sz[root] = 0;buildtree(root, 1, n, 0);
}
int main(void) {int n;while (~scanf("%d", &n) && n) {init(n);for (int i = 1; i <= n; i++) {scanf("%d", &nodes[i].val);nodes[i].id = i;}sort(nodes + 1, nodes + n + 1);for (int i = 1; i < n; i++) {splay(nodes[i].id, 0);update_rev(tree[root][0]);printf("%d ", i + sz[tree[root][0]]);del_root();}printf("%d\n", n);}return 0;
}

模板题③:HDU 3726 Graph and Queries
这题很经典,这里有Treap树的题解:https://blog.csdn.net/TK_wang_/article/details/108615120

#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 2e6+5;
#define Key_value tree[tree[root][1]][0]
int val[maxn], n, m, kase;
int tree[maxn][2],sz[maxn], pre[maxn], key[maxn];
int root, tag[maxn];
int fa[maxn];
struct edge{int u, v;
}e[maxn];
struct command{int x, k, cas;
}cmd[maxn];
void newnode(int& x, int fa, int pos, int v) {x = pos;tree[x][0] = tree[x][1] = 0;sz[x] = 1; pre[x] = fa; key[x] = v;
}
void pushup(int x) {sz[x] = sz[tree[x][0]] + sz[tree[x][1]] + 1;
}
void Rotate(int x, int c) {int y = pre[x];tree[y][!c] = tree[x][c];pre[tree[x][c]] = y;if (pre[y])tree[pre[y]][tree[pre[y]][1] == y] = x;pre[x] = pre[y];tree[x][c] = y;pre[y] = x;pushup(y);
}
void splay(int x, int goal) {while (pre[x] != goal) {if (pre[pre[x]] == goal) {//再转一次就完成了Rotate(x, tree[pre[x]][0] == x);}else {int y = pre[x];int c = (tree[pre[y]][0] == y);if (tree[y][c] == x) {//三点不共线Rotate(x, !c);Rotate(x, c);}else {//三点共线Rotate(y, c);Rotate(x, c);}}}pushup(x);if (goal == 0)root = x;
}
int kth(int x, int k) {int t = sz[tree[x][1]] + 1;if (k == t)return x;if (t < k)return kth(tree[x][0], k - t);else return kth(tree[x][1], k);
}
void INSERT(int pos, int v) {int x = root;if (!x) {newnode(x, 0, pos, v);return;}while (tree[x][key[x] < v]) {x = tree[x][key[x] < v];}newnode(tree[x][key[x] < v], x, pos, v);splay(tree[x][key[x] < v], 0);
}
int get_min(int rt) {while (tree[rt][0])rt = tree[rt][0];return rt;
}
void del_root() {if (tree[root][0] == 0 || tree[root][1] == 0) {root = tree[root][0] + tree[root][1];pre[root] = 0;return;}int k = get_min(tree[root][1]);splay(k, root);//把仅比root大的转为根,删除rootKey_value = tree[root][0];//显然k的左子树为空root = tree[root][1];pre[tree[root][0]] = root;//更新父亲pre[root] = 0;pushup(root);
}
int findfa(int x) {return x == fa[x] ? x : fa[x] = findfa(fa[x]);
}
void bing(int r) {if (!r)return;bing(tree[r][0]);bing(tree[r][1]);INSERT(r, val[r]);
}
void merge(int x, int y) {int fa1 = findfa(x), fa2 = findfa(y);if (fa1 == fa2)return;fa[fa1] = fa2;splay(fa1, 0);splay(fa2, 0);if (sz[fa1] > sz[fa2])swap(fa1, fa2);root = fa2;bing(fa1);splay(fa2, 0);
}
int main(void) {while (~scanf("%d %d", &n, &m) && (n || m)) {for (int i = 1; i <= n; i++) {scanf("%d", &val[i]);fa[i] = i;}for (int i = 1; i <= m; i++) {scanf("%d %d", &e[i].u, &e[i].v);tag[i] = 0;}int q_num = 0;while (1) {char str[5];scanf("%s", str);if (str[0] == 'E')break;q_num++;if (str[0] == 'D') {scanf("%d", &cmd[q_num].x);cmd[q_num].cas = 1;tag[cmd[q_num].x] = 1;}else if (str[0] == 'Q') {scanf("%d %d", &cmd[q_num].x,&cmd[q_num].k);cmd[q_num].cas = 2;}else {int x, v;scanf("%d %d", &x, &v);cmd[q_num].cas = 3;cmd[q_num].x = x;cmd[q_num].k = val[x];val[x] = v;}}for (int i = 1; i <= n; i++)newnode(root, 0, i, val[i]);for (int i = 1; i <= m; i++) {if (tag[i])continue;merge(e[i].u, e[i].v);}double ans = 0;int cnt = 0;for (int i = q_num; i; i--) {if (cmd[i].cas == 1) {int x = cmd[i].x;merge(e[x].u, e[x].v);              }else if (cmd[i].cas == 2) {int x = cmd[i].x, k = cmd[i].k;cnt++;splay(x, 0);if (sz[root] < k || k <= 0)continue;ans += key[kth(root, k)];}else {int x = cmd[i].x, v = cmd[i].k;splay(x, 0);val[root] = v;del_root();INSERT(x, v);}}if (cnt == 0)cnt++;printf("Case %d: %.6f\n", ++kase, ans / cnt);}return 0;
}

总结:Splay树的灵活度比较高,代码量大,要善于思考才能构造出符合题目要求的各种操作,它能做到一些线段树做不了的事情,是一种很有价值的数据结构。

这篇关于Splay树 模板题入门的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/898978

相关文章

Java Lettuce 客户端入门到生产的实现步骤

《JavaLettuce客户端入门到生产的实现步骤》本文主要介绍了JavaLettuce客户端入门到生产的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 目录1 安装依赖MavenGradle2 最小化连接示例3 核心特性速览4 生产环境配置建议5 常见问题

使用Java填充Word模板的操作指南

《使用Java填充Word模板的操作指南》本文介绍了Java填充Word模板的实现方法,包括文本、列表和复选框的填充,首先通过Word域功能设置模板变量,然后使用poi-tl、aspose-words... 目录前言一、设置word模板普通字段列表字段复选框二、代码1. 引入POM2. 模板放入项目3.代码

Python进行word模板内容替换的实现示例

《Python进行word模板内容替换的实现示例》本文介绍了使用Python自动化处理Word模板文档的常用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友... 目录技术背景与需求场景核心工具库介绍1.获取你的word模板内容2.正常文本内容的替换3.表格内容的

从入门到精通详解Python虚拟环境完全指南

《从入门到精通详解Python虚拟环境完全指南》Python虚拟环境是一个独立的Python运行环境,它允许你为不同的项目创建隔离的Python环境,下面小编就来和大家详细介绍一下吧... 目录什么是python虚拟环境一、使用venv创建和管理虚拟环境1.1 创建虚拟环境1.2 激活虚拟环境1.3 验证虚

Java List 使用举例(从入门到精通)

《JavaList使用举例(从入门到精通)》本文系统讲解JavaList,涵盖基础概念、核心特性、常用实现(如ArrayList、LinkedList)及性能对比,介绍创建、操作、遍历方法,结合实... 目录一、List 基础概念1.1 什么是 List?1.2 List 的核心特性1.3 List 家族成

c++日志库log4cplus快速入门小结

《c++日志库log4cplus快速入门小结》文章浏览阅读1.1w次,点赞9次,收藏44次。本文介绍Log4cplus,一种适用于C++的线程安全日志记录API,提供灵活的日志管理和配置控制。文章涵盖... 目录简介日志等级配置文件使用关于初始化使用示例总结参考资料简介log4j 用于Java,log4c

史上最全MybatisPlus从入门到精通

《史上最全MybatisPlus从入门到精通》MyBatis-Plus是MyBatis增强工具,简化开发并提升效率,支持自动映射表名/字段与实体类,提供条件构造器、多种查询方式(等值/范围/模糊/分页... 目录1.简介2.基础篇2.1.通用mapper接口操作2.2.通用service接口操作3.进阶篇3

Python自定义异常的全面指南(入门到实践)

《Python自定义异常的全面指南(入门到实践)》想象你正在开发一个银行系统,用户转账时余额不足,如果直接抛出ValueError,调用方很难区分是金额格式错误还是余额不足,这正是Python自定义异... 目录引言:为什么需要自定义异常一、异常基础:先搞懂python的异常体系1.1 异常是什么?1.2

Python实现Word转PDF全攻略(从入门到实战)

《Python实现Word转PDF全攻略(从入门到实战)》在数字化办公场景中,Word文档的跨平台兼容性始终是个难题,而PDF格式凭借所见即所得的特性,已成为文档分发和归档的标准格式,下面小编就来和大... 目录一、为什么需要python处理Word转PDF?二、主流转换方案对比三、五套实战方案详解方案1:

Spring WebClient从入门到精通

《SpringWebClient从入门到精通》本文详解SpringWebClient非阻塞响应式特性及优势,涵盖核心API、实战应用与性能优化,对比RestTemplate,为微服务通信提供高效解决... 目录一、WebClient 概述1.1 为什么选择 WebClient?1.2 WebClient 与