'commit'
This commit is contained in:
127
TangDou/AcWing/BalanceTree/TODO/253_Splay.cpp
Normal file
127
TangDou/AcWing/BalanceTree/TODO/253_Splay.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
const int N = 201000;
|
||||
// Splay树,没有仔细研究
|
||||
// 区间第k大就必须得splay了
|
||||
struct splay_tree {
|
||||
int ff, cnt, ch[2], val, size;
|
||||
} t[N];
|
||||
int root, tot;
|
||||
void update(int x) {
|
||||
t[x].size = t[t[x].ch[0]].size + t[t[x].ch[1]].size + t[x].cnt;
|
||||
}
|
||||
void rotate(int x) {
|
||||
int y = t[x].ff;
|
||||
int z = t[y].ff;
|
||||
int k = (t[y].ch[1] == x);
|
||||
t[z].ch[(t[z].ch[1] == y)] = x;
|
||||
t[x].ff = z;
|
||||
t[y].ch[k] = t[x].ch[k ^ 1];
|
||||
t[t[x].ch[k ^ 1]].ff = y;
|
||||
t[x].ch[k ^ 1] = y;
|
||||
t[y].ff = x;
|
||||
update(y);
|
||||
update(x);
|
||||
}
|
||||
void splay(int x, int s) {
|
||||
while (t[x].ff != s) {
|
||||
int y = t[x].ff, z = t[y].ff;
|
||||
if (z != s)
|
||||
(t[z].ch[0] == y) ^ (t[y].ch[0] == x) ? rotate(x) : rotate(y);
|
||||
rotate(x);
|
||||
}
|
||||
if (s == 0)
|
||||
root = x;
|
||||
}
|
||||
void find(int x) {
|
||||
int u = root;
|
||||
if (!u)
|
||||
return;
|
||||
while (t[u].ch[x > t[u].val] && x != t[u].val)
|
||||
u = t[u].ch[x > t[u].val];
|
||||
splay(u, 0);
|
||||
}
|
||||
void insert(int x) {
|
||||
int u = root, ff = 0;
|
||||
while (u && t[u].val != x) {
|
||||
ff = u;
|
||||
u = t[u].ch[x > t[u].val];
|
||||
}
|
||||
if (u)
|
||||
t[u].cnt++;
|
||||
else {
|
||||
u = ++tot;
|
||||
if (ff)
|
||||
t[ff].ch[x > t[ff].val] = u;
|
||||
t[u].ch[0] = t[u].ch[1] = 0;
|
||||
t[tot].ff = ff;
|
||||
t[tot].val = x;
|
||||
t[tot].cnt = 1;
|
||||
t[tot].size = 1;
|
||||
}
|
||||
splay(u, 0);
|
||||
}
|
||||
int Next(int x, int f) {
|
||||
find(x);
|
||||
int u = root;
|
||||
if (t[u].val > x && f)
|
||||
return u;
|
||||
if (t[u].val < x && !f)
|
||||
return u;
|
||||
u = t[u].ch[f];
|
||||
while (t[u].ch[f ^ 1])
|
||||
u = t[u].ch[f ^ 1];
|
||||
return u;
|
||||
}
|
||||
void Delete(int x) {
|
||||
int last = Next(x, 0);
|
||||
int Net = Next(x, 1);
|
||||
splay(last, 0);
|
||||
splay(Net, last);
|
||||
int del = t[Net].ch[0];
|
||||
if (t[del].cnt > 1) {
|
||||
t[del].cnt--;
|
||||
splay(del, 0);
|
||||
} else
|
||||
t[Net].ch[0] = 0;
|
||||
}
|
||||
int kth(int x) {
|
||||
int u = root;
|
||||
while (t[u].size < x)
|
||||
return 0;
|
||||
while (1) {
|
||||
int y = t[u].ch[0];
|
||||
if (x > t[y].size + t[u].cnt) {
|
||||
x -= t[y].size + t[u].cnt;
|
||||
u = t[u].ch[1];
|
||||
} else if (t[y].size >= x)
|
||||
u = y;
|
||||
else
|
||||
return t[u].val;
|
||||
}
|
||||
}
|
||||
int main() {
|
||||
int n;
|
||||
scanf("%d", &n);
|
||||
insert(1e9);
|
||||
insert(-1e9);
|
||||
while (n--) {
|
||||
int opt, x;
|
||||
scanf("%d%d", &opt, &x);
|
||||
if (opt == 1)
|
||||
insert(x);
|
||||
if (opt == 2)
|
||||
Delete(x);
|
||||
if (opt == 3) {
|
||||
find(x);
|
||||
printf("%d\n", t[t[root].ch[0]].size);
|
||||
}
|
||||
if (opt == 4)
|
||||
printf("%d\n", kth(x + 1));
|
||||
if (opt == 5)
|
||||
printf("%d\n", t[Next(x, 0)].val);
|
||||
if (opt == 6)
|
||||
printf("%d\n", t[Next(x, 1)].val);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
52
TangDou/AcWing/BalanceTree/TODO/253_Szsz.cpp
Normal file
52
TangDou/AcWing/BalanceTree/TODO/253_Szsz.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
//为什么这个平衡树也可以用树状数组来做呢?
|
||||
const int N = 1e5 + 10;
|
||||
|
||||
int n, tot, sz;
|
||||
int op[N], a[N], t[N], c[N];
|
||||
|
||||
int pos(int x) {
|
||||
return lower_bound(t + 1, t + 1 + sz, x) - t;
|
||||
}
|
||||
void add(int x, int k) {
|
||||
for (x; x <= sz; x += x & -x) c[x] += k;
|
||||
}
|
||||
int query(int x) {
|
||||
int tmp = 0;
|
||||
for (x; x; x -= x & -x) tmp += c[x];
|
||||
return tmp;
|
||||
}
|
||||
int kth(int x) {
|
||||
int rs = 0;
|
||||
for (int i = 17; i >= 0; --i) {
|
||||
rs += (1 << i);
|
||||
if (rs > sz || c[rs] >= x)
|
||||
rs -= (1 << i);
|
||||
else
|
||||
x -= c[rs];
|
||||
}
|
||||
return t[rs + 1];
|
||||
}
|
||||
int main() {
|
||||
scanf("%d", &n);
|
||||
//树状数组为啥需要离线处理呢
|
||||
for (int i = 1; i <= n; i++) {
|
||||
scanf("%d%d", &op[i], &a[i]);
|
||||
if (op[i] != 4) t[++tot] = a[i];
|
||||
}
|
||||
//离散化
|
||||
sort(t + 1, t + 1 + tot);
|
||||
sz = unique(t + 1, t + 1 + tot) - t - 1;
|
||||
|
||||
for (int i = 1; i <= n; i++) {
|
||||
int x = a[i];
|
||||
if (op[i] == 1) add(pos(x), 1);
|
||||
if (op[i] == 2) add(pos(x), -1);
|
||||
if (op[i] == 3) printf("%d\n", query(pos(x) - 1) + 1);
|
||||
if (op[i] == 4) printf("%d\n", kth(x));
|
||||
if (op[i] == 5) printf("%d\n", kth(query(pos(x) - 1)));
|
||||
if (op[i] == 6) printf("%d\n", kth(query(pos(x)) + 1));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
69
TangDou/AcWing/BalanceTree/TODO/253_Trie.cpp
Normal file
69
TangDou/AcWing/BalanceTree/TODO/253_Trie.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
// 571 ms
|
||||
const int N = 3200100;
|
||||
const int base = 1e7;
|
||||
|
||||
// 01Trie树
|
||||
int tr[N][2];
|
||||
int root = 1; //根是1号点
|
||||
int idx = 1; //节点号计数器
|
||||
int cnt[N]; //结点p的子结点的个数
|
||||
|
||||
//插入
|
||||
void insert(int x, int t) {
|
||||
x += base; //保证输入非负
|
||||
int p = root;
|
||||
for (int i = 31; ~i; i--) { //从高位到低位
|
||||
int c = x >> i & 1; //计算x在第i位是0还是1
|
||||
if (!tr[p][c]) tr[p][c] = ++idx; //如果想走的位置不存在,则创建之,++idx为新创建的节点的编号
|
||||
p = tr[p][c]; //游标p继续前行
|
||||
//这句是迎合本题特殊加入的记录数据
|
||||
cnt[p] += t; //以p为根的子树中节点个数
|
||||
}
|
||||
}
|
||||
|
||||
//获取小于数x的个数
|
||||
int rnk(int x) {
|
||||
x += base; //保证输入非负
|
||||
int p = root;
|
||||
int res = 0;
|
||||
for (int i = 31; ~i; i--) { //从高位到低位
|
||||
int c = x >> i & 1; //计算x在第i位是0还是1
|
||||
if (c) res += cnt[tr[p][0]]; //当该位为1时,找其他的该位为0的数的个数,就是严格小于这个数的个数了
|
||||
p = tr[p][c]; //游标p继续前行
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
//获取第k位置的数字
|
||||
int find_kth(int k) {
|
||||
int p = root;
|
||||
int res = 0;
|
||||
for (int i = 31; ~i; i--) { // ~i 表示 i>=0
|
||||
if (k > cnt[tr[p][0]]) //在右手边
|
||||
res += (1 << i), k -= cnt[tr[p][0]], p = tr[p][1];
|
||||
//(1) res += (1 << i) //恢复原始数据的拼接过程
|
||||
//(2) x -= cnt[tr[p][0]] 减去左手边的个数
|
||||
//(3) p = tr[p][1] 向右手边继续游标前进
|
||||
else
|
||||
p = tr[p][0]; //游标p向左移动进去,继续前行
|
||||
}
|
||||
return res - base; //因为我们之前加过一个base,所以这里要减去
|
||||
}
|
||||
|
||||
int main() {
|
||||
int n;
|
||||
scanf("%d", &n);
|
||||
while (n--) {
|
||||
int op, x;
|
||||
scanf("%d%d", &op, &x);
|
||||
if (op == 1) insert(x, 1); //插入x
|
||||
if (op == 2) insert(x, -1); //删除x
|
||||
if (op == 3) cout << rnk(x) + 1 << endl; //查询严格小于x的个数,再+1,就是x的排名
|
||||
if (op == 4) cout << find_kth(x) << endl; //查询第k位置的数
|
||||
if (op == 5) cout << find_kth(rnk(x)) << endl; // x的前驱(小于x的最大值)
|
||||
if (op == 6) cout << find_kth(rnk(x + 1) + 1) << endl; // x的后继(大于x的最小值)
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
41
TangDou/AcWing/BalanceTree/TODO/253_Trie.md
Normal file
41
TangDou/AcWing/BalanceTree/TODO/253_Trie.md
Normal file
@@ -0,0 +1,41 @@
|
||||
### 普通平衡树【01trie写法】
|
||||
主要就是$trie$的模板,这里对一些多出来的东西做一个解释
|
||||
|
||||
* $cnt[p]$
|
||||
表示插入数的时候经过这个结点的次数,其实我们稍微转换一下,也就是结点$p$的子结点的个数。
|
||||
|
||||
* $rnk(x)$
|
||||
表示的是小于$x$的数的个数
|
||||
|
||||
* $find_kth(k)$
|
||||
表示第k个数
|
||||
|
||||
于是我们可以知道对于每条询问的表达方式了
|
||||
1、$insert(x, 1)$
|
||||
2、$insert(x, -1)$
|
||||
3、$rnk(x) + 1$因为$rnk(x)$表示小于$x$的数的个数,加一就是$x$的位置了
|
||||
4、$find_kth(k)$ 查找第$k$个数字
|
||||
5、$find_kth(rnk(x))$
|
||||
名次$k$为小于$x$的数的个数,即$k = rnk(x)$,然后$find_kth(k)$就是小于$x$的最大值了。
|
||||
|
||||
6、类似$5$,我们做一个简单的推理,首先最后的表达式应该式类似于$find_kth(rank(x))$这样的形式的,然后需要查找的是大于$x$的最小值,也就是等价于先求小于等于$x$的数的个数,设为$k$然后求$find_kth(k + 1)$即可,也就是$find_kth(rnk(x + 1) + 1)$
|
||||
|
||||
代码难度比$treap$低了许多,写起来比较舒服,缺点是比较吃空间。
|
||||
|
||||
补充一下,因为输入有可能为负数,所以我们需要将所有数加上一个$base$,保证输入非负即可
|
||||
如果还看不明白的可以试着手写一个$trie$来模拟一下,就会有所理解了。
|
||||
|
||||
|
||||
已知最好写的平衡树,至少目前常数最小(测了大部分平衡树实现)
|
||||
|
||||
跑的比动态开点线段树什么的快多了
|
||||
|
||||
比$Splay$快$1.5-2$倍
|
||||
|
||||
唯一缺点是不能像$Splay$一样区间操作(不过能做这个的好像不多...)
|
||||
|
||||
还有内存占用较大,不过正常使用$1e5-3e5$几乎都是最大$30M$左右
|
||||
|
||||
主要是基于二进制分解的 $Trie$
|
||||
|
||||
同时满足二叉查找树,线段树和平衡树性质,具有高可扩展性
|
||||
211
TangDou/AcWing/BalanceTree/TODO/253_Tzy.cpp
Normal file
211
TangDou/AcWing/BalanceTree/TODO/253_Tzy.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
//替罪羊树,没有仔细研究
|
||||
|
||||
const int N = 100010;
|
||||
const double alpha = 0.75; // 啊啊啊 double !!
|
||||
|
||||
struct Scape_goat {
|
||||
int l, r;
|
||||
int val;
|
||||
int size; // 子树的大小(包括它自己), 包括被打了删除标记的结点
|
||||
int exist;
|
||||
int fact;
|
||||
} tr[N];
|
||||
int idx;
|
||||
int root;
|
||||
int rec[N], cnt;
|
||||
|
||||
// 每次新建节点只管他自己,不需要管儿子们
|
||||
void build(int &u, int val) {
|
||||
u = ++idx; // 开一个位置
|
||||
tr[u].val = val;
|
||||
tr[u].size = 1;
|
||||
tr[u].fact = 1;
|
||||
tr[u].exist = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重构条件 : 当前节点的左子树或右子树的size > 当前结点的大小 乘 一个平衡因子alpha (一般取0.75)
|
||||
* 或者以当前结点为根的子树中被删掉的点 > 该子树大小的 30%
|
||||
**/
|
||||
|
||||
bool isbalance(int u) {
|
||||
if (tr[tr[u].l].size > tr[u].size * alpha || tr[tr[u].r].size > tr[u].size * alpha) return false;
|
||||
if (tr[u].size - tr[u].fact > tr[u].size * 0.3) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重构: 将当前子树先中序遍历, 压扁,然后再拎起来
|
||||
**/
|
||||
|
||||
void inorder(int u) {
|
||||
if (!u) return;
|
||||
inorder(tr[u].l);
|
||||
if (tr[u].exist) rec[++cnt] = u;
|
||||
inorder(tr[u].r);
|
||||
}
|
||||
|
||||
// pushup 的时候, 由于没有记录子节点到父节点的指针, 所以只能头递归
|
||||
void pushup(int tmp, int op) {
|
||||
if (!tmp) return; // 整棵树都被删掉的情况
|
||||
if (tr[tmp].val > tr[op].val) {
|
||||
pushup(tr[tmp].l, op);
|
||||
} else
|
||||
pushup(tr[tmp].r, op);
|
||||
|
||||
tr[tmp].size = tr[tr[tmp].l].size + tr[tr[tmp].r].size + 1;
|
||||
// fact 不需要修改
|
||||
}
|
||||
|
||||
// l, r 指rec数组中存的子树中存在的结点
|
||||
// u 是l ,r 这段区间所构成子树的根
|
||||
void lift(int l, int r, int &u) {
|
||||
if (l == r) // 叶子结点
|
||||
{
|
||||
u = rec[l]; // 同样 传引用 将这个节点设置为他的父亲节点的儿子
|
||||
tr[u].l = tr[u].r = 0;
|
||||
tr[u].size = 1;
|
||||
tr[u].fact = 1;
|
||||
return;
|
||||
}
|
||||
int mid = (l + r) >> 1; // 找根
|
||||
// 因为 该树的定义为 左子树严格小于根, 所以 需要放置 连续mid左侧连续几个数相等的情况
|
||||
// 注意这儿的 l ,r mid 并不是结点, rec[l, r, mid] 才是结点!!!
|
||||
while (mid > l and tr[rec[mid]].val == tr[rec[mid - 1]].val) mid--;
|
||||
u = rec[mid];
|
||||
if (mid == l)
|
||||
tr[u].l = 0;
|
||||
else
|
||||
lift(l, mid - 1, tr[u].l); // 注意递归两边的时候传mid - 1 与 mid + 1, 千万不能有mid, 否则可能自己的根是自己,导致递归炸掉
|
||||
|
||||
lift(mid + 1, r, tr[u].r);
|
||||
tr[u].size = tr[u].fact = r - l + 1;
|
||||
}
|
||||
|
||||
void rebuild(int &u) {
|
||||
cnt = 0;
|
||||
inorder(u);
|
||||
if (cnt == 0) { // 这颗子树整体都应该被删掉
|
||||
u = 0; // 即他作为他的父节点的子结点变成了0, 传引用的好书
|
||||
return;
|
||||
}
|
||||
// 否则, 分治 拎起来
|
||||
lift(1, cnt, u);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并判断一棵树是否需要重构
|
||||
* 从根节点向当前被操作的结点一路判断, 找到第一个需要被重构的结点,并重构它
|
||||
* 注意顺序不能反, 如果反过来找,就可能重构好几次。
|
||||
**/
|
||||
|
||||
// tmp 是当前的结点, 从root 开始, op 代指刚被操作过的结点
|
||||
void check(int &tmp, int op) {
|
||||
if (tmp == op) return;
|
||||
if (!isbalance(tmp)) {
|
||||
rebuild(tmp);
|
||||
pushup(root, tmp); // 用子节点的size 更新父节点的size
|
||||
return;
|
||||
}
|
||||
if (tr[op].val < tr[tmp].val)
|
||||
check(tr[tmp].l, op);
|
||||
else
|
||||
check(tr[tmp].r, op);
|
||||
}
|
||||
|
||||
// 左边放小于根的, 右边放 >= 根的
|
||||
void insert(int &u, int val) // 上一层传来的参数是 tr[u].l 或 tr[u].r , 传引用相当于直接 修改 tr[u].l = xxx
|
||||
{
|
||||
if (!u) {
|
||||
build(u, val);
|
||||
check(root, u);
|
||||
return;
|
||||
}
|
||||
tr[u].size++;
|
||||
tr[u].fact++;
|
||||
if (val < tr[u].val)
|
||||
insert(tr[u].l, val);
|
||||
else
|
||||
insert(tr[u].r, val);
|
||||
}
|
||||
|
||||
// 懒惰删除, 只打标记不真删, 只减fact 不减 size
|
||||
void del(int u, int val) {
|
||||
tr[u].fact--;
|
||||
if (tr[u].exist and tr[u].val == val) {
|
||||
tr[u].exist = false;
|
||||
check(root, u);
|
||||
return;
|
||||
}
|
||||
if (val < tr[u].val)
|
||||
del(tr[u].l, val);
|
||||
else
|
||||
del(tr[u].r, val);
|
||||
}
|
||||
|
||||
// val 不一定存在
|
||||
int get_rank_by_num(int val) {
|
||||
int tp = root;
|
||||
int rank = 1;
|
||||
while (tp) {
|
||||
if (val <= tr[tp].val)
|
||||
tp = tr[tp].l;
|
||||
else {
|
||||
rank += tr[tr[tp].l].fact + tr[tp].exist;
|
||||
tp = tr[tp].r;
|
||||
}
|
||||
}
|
||||
return rank;
|
||||
}
|
||||
|
||||
int get_num_by_rank(int k) {
|
||||
int tp = root;
|
||||
while (tp) {
|
||||
if (tr[tr[tp].l].fact + tr[tp].exist == k and tr[tp].exist) break;
|
||||
if (tr[tr[tp].l].fact >= k) {
|
||||
tp = tr[tp].l;
|
||||
} else {
|
||||
k -= tr[tr[tp].l].fact + tr[tp].exist;
|
||||
tp = tr[tp].r;
|
||||
}
|
||||
}
|
||||
return tr[tp].val;
|
||||
}
|
||||
|
||||
int get_pre(int val) // 前驱
|
||||
{
|
||||
int k = get_rank_by_num(val);
|
||||
return get_num_by_rank(k - 1);
|
||||
}
|
||||
int get_next(int val) {
|
||||
// 注意不能 getnum(getrank(val) + 1) 防止 1224 这种情况, 排名x和x + 1可能是同一个数
|
||||
int k = get_rank_by_num(val + 1);
|
||||
return get_num_by_rank(k);
|
||||
}
|
||||
|
||||
int main() {
|
||||
int n;
|
||||
scanf("%d", &n);
|
||||
int opt, x;
|
||||
while (n--) {
|
||||
scanf("%d%d", &opt, &x);
|
||||
if (opt == 1) {
|
||||
insert(root, x);
|
||||
} else if (opt == 2) {
|
||||
del(root, x);
|
||||
} else if (opt == 3) {
|
||||
printf("%d\n", get_rank_by_num(x));
|
||||
} else if (opt == 4) {
|
||||
printf("%d\n", get_num_by_rank(x));
|
||||
} else if (opt == 5) {
|
||||
printf("%d\n", get_pre(x));
|
||||
} else if (opt == 6) {
|
||||
printf("%d\n", get_next(x));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user