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
*/ 
posted @ 2020-05-23 17:07  Luckyblock  阅读(218)  评论(1编辑  收藏  举报