标签:class namespace max 出现 display 排名 scan ota void
emmm....查了半天的错,原来是read()函数的问题,气skr人
splay树真快乐,好多实现的模版,总算找到一个容易的
老规矩,模版链接:https://blog.csdn.net/Emm_Titan/article/details/103910330
题目链接:https://www.luogu.com.cn/record/45785203
自己用数组实现+自己的救济理解
#include<iostream> using namespace std; #define INF 0x7f7f7f7f typedef long long ll; const int maxn = 1e5 + 100; inline int read() { int s = 0, w = 1; char ch = getchar(); while (ch < ‘0‘ || ch > ‘9‘) { if (ch == ‘-‘) w = -1; ch = getchar(); } while (ch >= ‘0‘ && ch <= ‘9‘) s = s * 10 + ch - ‘0‘, ch = getchar(); return s * w; } int tree[maxn][2], siz[maxn], cnt[maxn], pre[maxn], val[maxn]; //siz:子节点个数(包括自己),cnt:自己出现的次数,pre:父节点,val:节点权值,tree[x][0]=0表示x无左孩子,右孩子同理 int tot=0, root=0; void push_up(int x) {//每次旋转都需要更新节点左右子树个数 siz[x] = siz[tree[x][0]] + siz[tree[x][1]] + cnt[x]; } void rotate(int x) {//两种情况,如果是父节点的右孩子(f=0),就左旋(父节点变x左节点)和如果是左孩子(f=1),就右旋(父节点变x右节点); int y = pre[x], z = pre[y], f = (tree[y][0] == x); //需要更新两条边,f^1就是取反,1变0,0变1,可以再草稿本上画一下 //1:与父节点相连的那条边,父节点某孩子替换为x的某孩子节点,原本的x孩子节点替换为父节点 tree[y][f ^ 1] = tree[x][f]; pre[tree[x][f]] = y; tree[x][f] = y; pre[y] = x; //2:父节点与祖父节点相连的那条边,祖父节点不再接父节点而是x if (z) {tree[z][tree[z][1] == y] = x;} pre[x] = z; push_up(y);//先更新y再更新x,y已经是x的孩子节点了,即先更新孩子再更新自己 push_up(x); } void splay(int x,int top) {//将x旋转到为top的孩子,一般把top设置为0,即没有父亲,则旋转到根节点 //三种情况 while (pre[x] != top) { int y = pre[x], z = pre[y]; if (z != top) {//1如果祖父已经是终止节点top,直接把x旋转到父节点即可,不进入判断 //如果祖父不是终止节点,就把x旋转到祖父节点,需要判断三个节点是否共线(用异或,比较巧,所以可以当模板用) if (((tree[z][0] == y) ^ (tree[y][0] == x))== 1) {//如果不共线,就旋转x两次到祖父节点 rotate(x); } else {//如果共线就先旋转y,再旋转x rotate(y); } //为什么不不去判断是否共线,直接旋转两次x呢...百度了一下,没找到,我猜因为先旋转y就让树更平衡了,更接近平衡树 } rotate(x); } if (!top)root = x; } //后面就没啥技巧了,看源码基本都能看懂 void insert(int w) {//插入权值为w的值 int fa = 0, p = root;//先用中序遍历方法查找插入的位置 while (p && val[p] != w) {//终止条件为要么找到权值为w的点,要么找到其插入的父节点 fa = p; p = tree[p][w>val[p]]; } if (p) {//如果已经存在,+1即可 cnt[p]++; } else {//不存在就申请节点,把数据都设置为初始值 p = ++tot; tree[p][0] = tree[p][1] = 0; siz[p] = cnt[p] = 1; val[p] = w; if (fa) { tree[fa][w > val[fa]] = p; } pre[p] = fa; } splay(p, 0);//注意要把刚插入的值旋转到根,因为你刚更新了个数啊什么的,需要push_up去更新上面的点的数据 } void find(int w) {//找到最接近w的值把他旋转到根节点 if (!root)return; int p = root; while (tree[p][w >val[p]] && val[p] != w) { p = tree[p][w>val[p]]; } splay(p, 0); } int find_kth(int k){//查找排名为k的节点 int p = root; if (siz[p] < k)return -1; while (1) { int l = tree[p][0], r = tree[p][1]; if (k<=siz[l])p = l;//这里要超级小心啊,判断要想清楚 else if (k<=cnt[p] + siz[l])return p; else { k -= (siz[l] + cnt[p]); p = r; } } } int pre_suf(int w,int f) {//找到前驱或者后继,f为0前驱,1为后继 find(w);//先将与w最接近的节点旋转到根 int p = root; if (val[p] > w && f) {//如果p节点权值比w>而且是查后继,那么p就刚好是结果 return p; } if (val[p] < w && !f) {//同理 return p; } //都不是的话,找后继就是右子树最小节点(右子树最左的那个节点),找前驱就是左子树最大节点(左子树最右的那个节点) p = tree[p][f]; if (!p)return 0; while (tree[p][f ^ 1]) { p = tree[p][f ^ 1]; } return p; } void del(int x) {//删除权值为x的节点 //先找到x的前驱节点与后继节点,然后把前驱节点旋转到根,后继节点旋转到前驱节点的右孩子位置 //于是乎权值为x的节点比在后继节点的左子树位置 //因为pre<x<suf然后suf又刚好在pre的右孩子位置,然后前驱和后继又都是最接近x的,所以....必定在,除非树里面没有这个节点x //这个是我看到模板里面实现最简单的了 int pre = pre_suf(x, 0), suf = pre_suf(x, 1); splay(pre, 0); splay(suf, pre);//pre可能为0 int p = tree[suf][0]; if (cnt[p] > 1) { cnt[p]--; splay(p, 0);//每次涉及到修改的都要splay啊!!!! } else { tree[suf][0] = 0; } } int main() { //freopen("test.txt", "r", stdin); insert(INF); insert(-INF); int n; scanf("%d", &n); for (int i = 0; i < n; i++) { int cmd, x; scanf("%d%d", &cmd, &x); if (cmd == 1) { insert(x); } else if (cmd == 2) { del(x); } else if (cmd == 3) { find(x);//查找w的rank就把他旋转到根,左孩子个数就是rank,本来要+1的,但是由于有哨兵-INT的存在,就不用啦 printf("%d\n", siz[tree[root][0]]); } else if (cmd == 4) { printf("%d\n", val[find_kth(x + 1)]); } else if (cmd == 5) { printf("%d\n", val[pre_suf(x, 0)]); } else { printf("%d\n", val[pre_suf(x, 1)]); } } return 0; }
标签:class namespace max 出现 display 排名 scan ota void
原文地址:https://www.cnblogs.com/MYMYACMer/p/14348972.html