P3285 [SCOI2014]方伯伯的OJ
P3285 SCOI2014方伯伯的OJ
标签:Splay
如果直接使用平衡树维护编号,空间会开不下()
注意到操作只有1e5个,则有些区间的编号在程序结束时仍然没有被改变(即:结束时区间中的编号仍然是 )
于是一个节点里可以存一个 表示这个节点存了编号为 到 的所有节点,这个节点的 设为 (区间长度)
每次根据排名查找节点的时候类似根据排名找节点
如果要对一个用户 (编号为) 进行操作,先找到这个值所在的节点 ,将这个节点分为三部分()
?如何找到编号为 对应的节点?
!使用 存储右端点对应的节点下标,查询节点下标时即可
?如何实现分裂
!实现函数 表示将节点 分裂为两部分,其中前 个仍然在 ,返回一个新的节点包含后 个数
!于是只需两次 即可将区间分为三份(注意或等情况)
点击查看代码
#include <map>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
constexpr int N = 5e6 + 5;
int n, q;
std::map<int, int> map;
struct Node {
int son[2], fa;
int l, r; // 这段区间存的是编号[l,r]的节点
int size;
} tr[N];
int root, idx;
inline void push_up(int u) {
tr[u].size = tr[tr[u].son[0]].size + tr[tr[u].son[1]].size + (tr[u].r - tr[u].l + 1);
}
void rotate(int x) {
int y = tr[x].fa, z = tr[y].fa, k = tr[y].son[1] == x;
tr[tr[z].son[tr[z].son[1] == y] = x].fa = z;
tr[tr[y].son[k] = tr[x].son[!k]].fa = y;
tr[tr[x].son[!k] = y].fa = x;
push_up(y), push_up(x);
}
void splay(int x, int rt) {
while(tr[x].fa != rt) {
int y = tr[x].fa, z = tr[y].fa;
if(z != rt) rotate((tr[y].son[1] == x) ^ (tr[z].son[1] == y) ? x : y);
rotate(x);
}
if(!rt) root = x;
}
// 将节点u分裂:u保留<=k的,返回一个新的节点(后若干)
int split(int u, int k) {
int v = ++ idx;
tr[v].l = k + 1, tr[v].r = tr[u].r;
tr[u].r = k;
if(tr[u].son[1]) {
int t = tr[u].son[1];
while(tr[t].son[0]) t = tr[t].son[0];
tr[tr[t].son[0] = v].fa = t;
} else tr[tr[u].son[1] = v].fa = u;
splay(v, 0);
return v;
}
// 将编号为id的节点拉出来,返回其编号,更新其余块(不更新自己)
int split3(int id) {
std::map<int, int>::iterator it = map.lower_bound(id);
if(it == map.end()) exit(0x7fffffff);
int u = it->second; // 得到id所在块
if(id < tr[u].r) it->second = split(u, id); // 右边分裂
else map.erase(it); // 没有右边了:暂时删除
if(id > tr[u].l) map[id - 1] = u, u = split(u, id - 1); // 左边分裂,左边单独为一块
return u;
}
// 得到排名
int getrnk(int u) {
splay(u, 0); // 旋转到根,左子树大小
return tr[tr[u].son[0]].size + 1;
}
// 重命名id为newid
int rename(int id, int newid) {
int u = split3(id); // 找到这个节点
map[tr[u].l = tr[u].r = newid] = u; // 更新id
return getrnk(u);
}
// “暂时”删除
void remove_temp(int u) {
splay(u, 0);
tr[tr[u].son[0]].fa = tr[tr[u].son[1]].fa = 0;
if(tr[u].son[0]) {
int t = tr[u].son[0];
while(tr[t].son[1]) t = tr[t].son[1];
splay(t, 0);
tr[tr[t].son[1] = tr[u].son[1]].fa = t;
push_up(t);
} else root = tr[u].son[1];
}
// 移到最前面
int move_to_front(int id) {
int u = split3(id); // 找到这个节点
map[tr[u].l] = u; // 此时必有tr[u].l=tr[u].r;更新id
int ret = getrnk(u);
remove_temp(u);
tr[u].son[0] = tr[u].son[1] = tr[u].fa = 0;
int v = root;
while(tr[v].son[0]) v = tr[v].son[0];
tr[tr[v].son[0] = u].fa = v;
splay(u, 0);
return ret;
}
// 移到最后面
int move_to_back(int id) {
int u = split3(id);
map[tr[u].l] = u;
int ret = getrnk(u);
remove_temp(u);
tr[u].son[0] = tr[u].son[1] = tr[u].fa = 0;
int v = root;
while(tr[v].son[1]) v = tr[v].son[1];
tr[tr[v].son[1] = u].fa = v;
splay(u, 0);
return ret;
}
// 根据排名得到编号
int getid(int k) {
int u = root, lssz;
while(u) {
lssz = tr[tr[u].son[0]].size;
if(lssz >= k) u = tr[u].son[0];
else if(lssz + (tr[u].r - tr[u].l + 1) >= k) return tr[u].l + (k - lssz) - 1;
else k -= lssz + (tr[u].r - tr[u].l + 1), u = tr[u].son[1];
}
return 0; // 不存在
}
// 初始化
void init() {
root = ++ idx;
tr[idx].l = 1, tr[idx].r = n, tr[idx].size = n;
map[n] = idx;
}
int main() {
scanf("%d%d", &n, &q), init();
for(int i = 1, op, x, y, a = 0; i <= q; i ++) {
scanf("%d%d", &op, &x);
if(op == 1) scanf("%d", &y), a = rename(x - a, y - a);
else if(op == 2) a = move_to_front(x - a);
else if(op == 3) a = move_to_back(x - a);
else a = getid(x - a);
printf("%d\n", a);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 提示词工程——AI应用必不可少的技术
· 地球OL攻略 —— 某应届生求职总结
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界