P3285 [SCOI2014]方伯伯的OJ

P3285 SCOI2014方伯伯的OJ
标签:Splay

如果直接使用平衡树维护编号,空间会开不下(\(10^8\)
注意到操作只有1e5个,则有些区间的编号在程序结束时仍然没有被改变(即:结束时区间中的编号仍然是 \([l,r]\)
于是一个节点里可以存一个 \([l,r]\) 表示这个节点存了编号为 \(l\)\(r\) 的所有节点,这个节点的 \(\texttt{size}\) 设为 \(\texttt{r-l+1}\)(区间长度)
每次根据排名查找节点的时候类似根据排名找节点
如果要对一个用户 (编号为\(k\)) 进行操作,先找到这个值所在的节点 \(u\),将这个节点分为三部分(\([l_u,k),\{k\},(k,r_u]\)
?如何找到编号为 \(k\) 对应的节点?
!使用 \(\texttt{std::map<int,int>}\) 存储右端点对应的节点下标,查询节点下标时\(\texttt{lower_bound(编号)}\)即可
?如何实现分裂
!实现函数 \(\texttt{int split(int u, int k);}\) 表示将节点 \(u\) 分裂为两部分,其中前 \(k\) 个仍然在 \(u\) ,返回一个新的节点包含后 \(k\) 个数
!于是只需两次 \(\texttt{split}\) 即可将区间分为三份(注意\(k=0\)\(k=size\)等情况)

点击查看代码
#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;
}
posted @ 2022-09-28 12:02  azzc  阅读(16)  评论(0编辑  收藏  举报