P2596 [ZJOI2006]书架
知识点: 平衡树
原题面
题目要求
给定一书列,从 \(1\sim n\) 依次编号。
给定下列五种操作 :
\(\text{Top}\ S\) :将编号S的书 放在最上面。
\(\text{Bottom}\ S\) :将编号S的书 放在最下面。
\(\text{Insert}\ S\ T,T\in[-1,1]\):将编号为S的书 与 编号为S+T的书交换位置。
\(\text{Ask}\ S\) :询问编号为S的书的排名。
\(\text{Query}\ S\):询问排名为S的书的编号。
\(n,m \le 80000\)
分析题意
乍一眼看上去和 水题P3850 [TJOI2007]书架 神似。
但水题只有排名和书名的单向查询,本题有排名和编号的双向查询,以及奇怪的操作。
则可选择建立映射关系:
每一本书 都对应 一个节点。
节点信息中储存 书的编号。
position[i] 维护 编号为 i 的书 对应的的节点编号。
同 [TJOI2007]书架,平衡树结构 维护在序列中的排名。
对于两种查询操作:
- 查询排名:将编号为 S 的书 对应的的节点旋转至根,输出其左子树大小。
- 查询权值:经典查询第k大操作。
置顶操作
- 可将原序列分为三份:[1,S-1], [S], [S+1,n]。
手玩后发现,置顶S即将原序列变为 [S], [1,S-1], [S+1,n]。
[1, S-1] 与 [S+1,n] 内部排列顺序不变。 - 由此考虑置顶S后 树的结构的变化。
将 S 旋转至根,则其左子树为 [1, S-1] ,右子树为[S + 1, n]。
由[1, S-1] 与 [S+1,n] 内部排列顺序不变,则左右子树内部结构不变,可将其看作两节点。
\(\Longrightarrow\)
显然可以通过 将[1,S-1] 变为 [S+1,n] 的左子树,来实现[S]的置顶。 - 再将 [1,S-1] 与 [S+1,n] 看作两子树。
可使[1, S-1] 成为[S+1, n] 中排名最小的节点 (即最左的节点) 的左子树。
-此操作只改变平衡树结构,不改变 包括position的各种维护的信息。
置底操作
- 原理与置顶操作相同。
将S旋转至根,使右子树成为 左子树中最右节点 即可。
相邻元素交换操作
- 直接与该元素的前驱/后继交换 位置及信息。
注意更新 poistion 的值。
代码实现
Splay实现比较好理解。
//splay
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <algorithm>
#define min std::min
#define max std::max
#define fat (t[now].fa)
#define ls (t[now].son[0])
#define rs (t[now].son[1])
#define ll long long
const int kMaxn = 1e5 + 10;
//=============================================================
struct SplayNode {
int son[2], fa, size, val;
} t[kMaxn << 1];
int kN, kM, node_num, root;
int original_rank[kMaxn], position[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Pushup(int now) {
if(! now) return ;
t[now].size = 1;
if(ls) position[t[t[now].son[0]].val] = t[now].son[0], t[now].size += t[ls].size;
if(rs) position[t[t[now].son[1]].val] = t[now].son[1], t[now].size += t[rs].size;
}
bool GetSonNum(int now) {
return now == t[t[now].fa].son[1];
}
void Rotate(int now) {
int fa = fat, gfa = t[fat].fa, which_son = GetSonNum(now);
t[fa].son[which_son] = t[now].son[which_son ^ 1];
t[t[now].son[which_son ^ 1]].fa = fa;
t[now].son[which_son ^ 1] = fa, t[fa].fa = now;
t[now].fa = gfa;
if(gfa) t[gfa].son[fa == t[gfa].son[1]] = now;
Pushup(fa), Pushup(now);
}
void Splay(int now) {
for (int fa = fat; (fa = fat) != 0; Rotate(now))
if (t[fa].fa) Rotate(GetSonNum(now) == GetSonNum(fa) ? fa : now);
root = now;
}
void Insert(int ori_rank, int value) {
if (! root) {
root = ++ node_num;
t[node_num].size = 1;
original_rank[node_num] = ori_rank;
t[node_num].val = value;
return ;
}
int now = root, fa = 0;
while (1) {
fa = now, now = t[now].son[original_rank[now] < ori_rank];
if (! now) {
++ node_num;
t[node_num].fa = fa, original_rank[node_num] = ori_rank;
t[node_num].val = value, t[node_num].size = 1;
t[fa].son[original_rank[fa] < ori_rank] = node_num;
Pushup(fa), Pushup(node_num);
Splay(node_num); return ;
}
}
}
void PutBookTop(int now) {
Splay (now);
if (! t[now].son[0]) return ; //特判
if (! t[now].son[1]) {
t[now].son[1] = t[now].son[0];
t[now].son[0] = 0;
return ;
}
int suc = t[root].son[1];
while (t[suc].son[0]) suc = t[suc].son[0];
t[suc].son[0] = t[root].son[0];
t[t[root].son[0]].fa = suc;
t[root].son[0] = 0;
Splay(suc);
}
void PutBookBottom(int now) {
Splay(now);
if (! t[now].son[1]) return ; //特判
if (! t[now].son[0]) {
t[now].son[0] = t[now].son[1];
t[now].son[1] = 0;
return ;
}
int pre = t[root].son[0];
while (t[pre].son[1]) pre = t[pre].son[1];
t[pre].son[1] = t[root].son[1];
t[t[root].son[1]].fa = pre;
t[root].son[1] = 0;
Splay(pre);
}
void InsertBook(int now, int T) {
if (T == 0) return ;
Splay(position[now]);
int pre = t[root].son[0], suc = t[root].son[1];
while (t[pre].son[1]) pre = t[pre].son[1];
while (t[suc].son[0]) suc = t[suc].son[0];
int target = T == 1 ? suc : pre;
int x1 = t[target].val, x2 = position[now];
std :: swap(position[now], position[x1]);
std :: swap(t[x2].val, t[target].val);
}
int QueryRank(int now) {
Splay(now);
return t[t[now].son[0]].size;
}
int Kth(int rank) {
for(int now = root; ; ) {
if (rank <= t[t[now].son[0]].size) {
now = t[now].son[0];
continue;
}
if (rank == t[t[now].son[0]].size + 1) return t[now].val;
rank -= (t[t[now].son[0]].size + 1); now = t[now].son[1];
}
}
//=============================================================
int main() {
kN = read(), kM = read();
for (int i = 1; i <= kN; ++ i) {
int value = read();
position[value] = i; Insert(i, value);
}
for (int i = 1; i <= kM; ++ i) {
char opt[10]; scanf("%s", opt);
int S = read(), T;
if (opt[0] == 'T') PutBookTop(position[S]);
if (opt[0] == 'B') PutBookBottom(position[S]);
if (opt[0] == 'I') T = read(), InsertBook(S, T);
if (opt[0] == 'A') printf("%d\n", QueryRank(position[S]));
if (opt[0] == 'Q') printf("%d\n", Kth(S));
}
return 0;
}
Fhq-Treap
参考了 Brave_Cattle 的博客
Fhq-treap 可维护一棵树的中序遍历结果,但是不支持通过编号来找节点。
维护了每个节点的父亲,即可求出某节点是中序遍历中的排名。
//Fhq
/*
By:Luckyblock
*/
#include <cstdio>
#include <bitset>
#include <ctype.h>
#include <algorithm>
#define min std::min
#define max std::max
#define ll long long
const int MARX = 2e5 + 10;
//=============================================================
struct FhqTreapNode
{
int son[2], size, fa, val, Rand;
} t[MARX << 1];
int NodeNum, Root, Pos[MARX];
int tmp1, tmp2, tmp3, tmp4, tmp5, tmp6;
//=============================================================
inline int read()
{
int f = 1, w = 0; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Update(int x)
{
t[x].size = 1;
if(t[x].son[0]) t[x].size += t[t[x].son[0]].size, t[t[x].son[0]].fa = x;
if(t[x].son[1]) t[x].size += t[t[x].son[1]].size, t[t[x].son[1]].fa = x;
}
int NewNode(int Val)
{
NodeNum ++;
t[NodeNum].size = 1, t[NodeNum].val = Val, t[NodeNum].Rand = rand();
return NodeNum;
}
void Split(int now, int Size, int &x, int &y)
{
if(! now) {x = y = 0; return ;}
if(Size <= t[t[now].son[0]].size) y = now, Split(t[now].son[0], Size, x, t[y].son[0]);
else x = now, Split(t[now].son[1], Size - t[t[now].son[0]].size - 1, t[x].son[1], y);
Update(now);
}
int Merge(int x, int y)
{
if(! x || ! y) return x + y;
Update(x), Update(y);
if(t[x].Rand < t[y].Rand)
{
t[x].son[1] = Merge(t[x].son[1], y);
Update(x);
return x;
}
t[y].son[0] = Merge(x, t[y].son[0]);
Update(y);
return y;
}
int Kth(int now, int Rank)
{
while(1)
{
if(Rank <= t[t[now].son[0]].size) {now = t[now].son[0]; continue;}
if(Rank == t[t[now].son[0]].size + 1) return now;
Rank -= t[t[now].son[0]].size + 1, now = t[now].son[1];
}
}
int QueryRank(int now)
{
int ret = t[t[now].son[0]].size + 1;
for(; t[now].fa; now = t[now].fa)
if(now == t[t[now].fa].son[1])
ret += t[t[t[now].fa].son[0]].size + 1;
return ret;
}
//=============================================================
int main()
{
int N = read(), M = read();
for(int i = 1; i <= N; ++ i)
{
int Val = read(); Pos[Val] = NewNode(Val);
Root = Merge(Root, Pos[Val]);
}
for(int i = 1; i <= M; ++ i)
{
char opt[10]; scanf("%s", opt);
int S = read(), T, X;
if(opt[0] == 'T')
{
int pos = QueryRank(Pos[S]);
Split(Root, pos - 1, tmp1, tmp2);
Split(tmp2, 1, tmp3, tmp4);
Root = Merge(Merge(tmp3, tmp1), tmp4);
}
if(opt[0] == 'B')
{
int pos = QueryRank(Pos[S]);
Split(Root, pos - 1, tmp1, tmp2);
Split(tmp2, 1, tmp3, tmp4);
Root = Merge(Merge(tmp1, tmp4), tmp3);
}
if(opt[0] == 'I')
{
T = read();
if(! T) continue;
int pos = QueryRank(Pos[S]);
Split(Root, pos - 1, tmp1, tmp2);
Split(tmp2, 1, tmp3, tmp4);
if(T == - 1)
Split(tmp1, pos - 2, tmp5, tmp6),
Root = Merge(Merge(Merge(tmp5, tmp3), tmp6), tmp4);
else
Split(tmp4, 1, tmp5, tmp6),
Root = Merge(Merge(Merge(tmp1, tmp5), tmp3), tmp6);
}
if(opt[0] == 'A') printf("%d\n", QueryRank(Pos[S]) - 1);
if(opt[0] == 'Q') printf("%d\n", t[Kth(Root, S)].val);
}
return 0;
}
/*
如何评价“我谔谔”在OI圈(尤其是洛谷圈)的过度使用? - 知乎
https://www.zhihu.com/question/385309518/answer/1134771543
*/
作者@Luckyblock,转载请声明出处。