这道题啊。。。
一开始学习的时候遇到了一篇讲的超级好的博客:rentenglong 的博客
这篇大家一定要去瞅瞅,讲的炒鸡详细(就是代码有点锅。。。)
后来找到一份和TA码风差不多的,才完善了一哈qwq
不过最终还是过了
Code:
#include <bits/stdc++.h> #define root e[0].son[1] using namespace std; int read() { int re = 0, f = 1; char ch = getchar(); while (ch < '0' || ch > '9') {if (ch == '-') f = -f; ch = getchar();} while ('0' <= ch && ch <= '9') {re = re * 10 + ch - '0'; ch = getchar();} return re * f; } const int N = 1e5 + 3; const int INF = 1e7 + 3; int n, cnt; struct node{ int v, father;//储存键值,父亲节点 int son[2];//存储左右孩子,son[0]为左,son[1]为右 int sum;//存储这个节点子树共有多少 元素 (元素包括sum + recy) int recy;//存储相同键值重复多少次 }e[N]; void update(int x) {//合并 e[x].sum = e[e[x].son[0]].sum + e[e[x].son[1]].sum + e[x].recy; } int identify(int x) {//确定当前节点与父亲节点的关系 return e[e[x].father].son[0] == x ? 0 : 1; } void connect(int x, int fa, int son) {//连接两个节点 e[x].father = fa; e[fa].son[son] = x; } void rotate(int x) {//核心函数之一,旋转 int y = e[x].father; int mroot = e[y].father; int mrootson = identify(y); int yson = identify(x); int B = e[x].son[yson ^ 1]; connect(B, y, yson); connect(y, x, (yson ^ 1)); connect(x, mroot, mrootson); update(y);//y在上,先更新y update(x); } void splay(int at, int to) {//核心函数 to = e[to].father; while (e[at].father != to) { int up = e[at].father; if (e[up].father == to) { rotate(at); } else if (identify(at) == identify(up)) {//当自己的父亲与爷爷在一条直线上时,先旋转父亲 rotate(up); rotate(at); } else {//不在同一直线上就旋转两次 rotate(at); rotate(at); } } } int newpoint(int v, int fa) {//添加新节点 e[++cnt].father = fa; e[cnt].v = v; e[cnt].sum = e[cnt].recy = 1; return cnt; } void Insert(int x) { int now = root; if(!root){//特判没根的情况 newpoint(x, 0); root = cnt; } else { while (true) { e[now].sum++;//经过的点子树中元素都要++ if (e[now].v == x) { e[now].recy++;//重复 splay(now, root); return; } int next = x < e[now].v ? 0 : 1;//判断该走左孩子还是右孩子 if (!e[now].son[next]) { int add = newpoint(x, now); e[now].son[next] = add; splay(add, root); return; } now = e[now].son[next]; } } } int find(int v) {//查询键值为v的点的位置 int now = root; while (true) { if (e[now].v == v) { splay(now, root);//划重点,下面有用 return now; } int next = v < e[now].v ? 0 : 1; if (!e[now].son[next]) { return 0; } now = e[now].son[next]; } } void delet(int x) {//删除节点 int now = find(x);//注意find()函数中已经将x转为根节点 if (!now) { return; } if (e[now].recy > 1) {//如果有重复,直接减去即可 e[now].recy--; e[now].sum--; } else { if (!e[now].son[0] && !e[now].son[1]) {//如果没有左右孩子,说明就一个点 root = 0; } else if (!e[now].son[0]) {//如果没有左孩子,直接将右孩子提为根节点 root = e[now].son[1]; e[root].father = 0; } else {//左右孩子都有,先将左子树中最大的节点转到左孩子的位置,然后提为根节点,把右孩子连到该节点之下 int lef = e[now].son[0]; while (e[lef].son[1]) {//找左子树中最大的节点 lef = e[lef].son[1]; } splay(lef, e[now].son[0]); connect(e[now].son[1], lef, 1); connect(lef, 0, 1); update(lef); } } } int _rank(int v) { int now = find(v);//因为find()已经将v转为根节点,所以直接求即可 return e[e[now].son[0]].sum + 1; } int arank(int x) { int now = root; while (true) { int used = e[now].sum - e[e[now].son[1]].sum; //如果x大于左子树元素,并且小于等于当前点的左子树的sum加上本节点的recy的值,那么当前的点就是要找的点 //因为此时说明该x是当前节点重复值中的一个 if (e[e[now].son[0]].sum < x && x <= used) { splay(now, root); return e[now].v; } if (x < used) {//小于则向左子树寻找 now = e[now].son[0]; } else {//大于则减去该值,再向右子树寻找 x -= used; now = e[now].son[1]; } } } int lower(int v) {//前驱 int now = root; int ans = -INF; while (now) { if (e[now].v < v && e[now].v > ans) { ans = e[now].v; } if (v > e[now].v) { now = e[now].son[1]; } else { now = e[now].son[0]; } } return ans; } int upper(int v) {//后继 int now = root; int ans = INF; while(now) { if (e[now].v > v && e[now].v < ans) { ans = e[now].v; } if (v < e[now].v) { now = e[now].son[0]; } else { now = e[now].son[1]; } } return ans; } int main () { n = read(); while (n--) { int o = read(); int x = read(); if (o == 1) { Insert(x); } else if (o == 2) { delet(x); } else if (o == 3) { printf("%d\n", _rank(x)); } else if (o == 4) { printf("%d\n", arank(x)); } else if (o == 5) { printf("%d\n", lower(x)); } else if (o == 6) { printf("%d\n", upper(x)); } } return 0; }