P2596 [ZJOI2006]书架 题解
题目传送门:link。
FHQ-Treap#
解题的关键在于如何来求出一本书上面有多少本书,但考虑到我们里面没有像权值一样的东西来让我们用按值分裂来完成这个操作,所以考虑用按排名分裂来实现。
我们按照先后顺序把所有的书都给插入到平衡树里面,根据二叉搜索树的性质,当前结点的所有左子树的结点都是在当前结点代表的书的上面的书,但是其实并不只是当前结点的左子树,比如下面的图:
假设我们要查询
我们能发现一个规律就是我们从当前的点开始算上当前结点的左子树大小,然后如果当前点是父节点的右儿子,那么我们跳到父节点,然后这时当前点的左子树的所有点都是在要查询的点代表的书的上方,所以我们累加当前点左子树的左子树大小加一,因为还有当前点也是在我们要查询的点代表的书的上方的。我们一直到了根节点才停止,这样就求出了当前点代表的书上方有几本书了。
用代码实现起来就是这样的:
inline int fid(int x)
{
int xx = x, res = siz[ch[x][0]] + 1;
while(xx != rt && x)
{
if(ch[fa[x]][1] == x) res += siz[ch[fa[x]][0]] + 1;
x = fa[x];
}
return res;
}
题目中的各项操作都是基于给定书的上方有几本书来解决的,所以我们现在的主要问题就是父节点。
我们在分裂和合并的时候,要顺便维护一下当前点的父节点是谁,比如在分裂的过程中,我们多传两个参数表示当前点的候选父节点,也就是一个是左部分的当前分裂到的上一个结点和右部分分裂到的上一个结点,如果要是当前点的左子树的结点数小于
我们在一开始把所有书插入平衡树的时候是直接插到后面的,需要开一个数组来记录对应的书的编号在平衡树中结点的编号,在掉用上面的 fid
的时候直接用题目给的书的编号来查找当前编号的书的上面有多少本书即可。
对于 Insert
操作是
code:
#include <bits/stdc++.h>
#define int long long
#define N 100100
#define endl '\n'
using namespace std;
int n, m, id[N], rt, cnt, ch[N][2], fa[N], siz[N], key[N], val[N];
inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}
inline void push_up(int x) {siz[x] = 1 + siz[ch[x][0]] + siz[ch[x][1]];}
inline int node(int x)
{
cnt ++;
val[cnt] = x;
key[cnt] = rand();
siz[cnt] = 1;
id[x] = cnt;//记录当前值在平衡树中的结点中的编号
return cnt ;
}
inline void split(int x, int k, int &xx, int &yy, int fx = 0, int fy = 0)
{
if(x == 0) return xx = yy = 0, void();//下面要维护父节点的信息
if(siz[ch[x][0]] + 1 <= k) fa[x] = fx, xx = x, split(ch[x][1], k - siz[ch[x][0]] - 1, ch[x][1], yy, x, fy);
else fa[x] = fy, yy = x, split(ch[x][0], k, xx, ch[x][0], fx, x);
push_up(x);
}
inline int merge(int x, int y)
{
if(! x || ! y) return x + y;
if(key[x] < key[y])
{
ch[x][1] = merge(ch[x][1], y);
fa[ch[x][1]] = x;//维护父节点的信息
push_up(x);
return x;
}
else
{
ch[y][0] = merge(x, ch[y][0]);
fa[ch[y][0]] = y;
push_up(y);
return y;
}
}
inline int fid(int x)//查找当前编号的书上面有多少本书
{
int xx = x, res = siz[ch[x][0]] + 1;
while(xx != rt && x)
{
if(ch[fa[x]][1] == x) res += siz[ch[fa[x]][0]] + 1;
x = fa[x];
}
return res;
}
signed main()
{
srand((unsigned)time(NULL));
string op;
n = read(), m = read();
for(int i = 1; i <= n; i ++)
{
int a = read();
rt = merge(rt, node(a));
}
for(int i = 1; i <= m; i ++)
{
int x, y, z, z1, k, o;
cin >> op;
o = read();
// if(op[0] != 'Q')
// cout << "find:" << fid(id[o]) << endl;
if(op[0] == 'T')
{
int xx = fid(id[o]);//找到上面有几本书之后按排名分裂
split(rt, xx, x, y);
split(x, xx - 1, x, z);
rt = merge(z, merge(x, y));//把当前编号的书放到顶端
}
if(op[0] == 'B')
{
int xx = fid(id[o]);//同理把当前编号的书放到最下面
split(rt, xx, x, y, 0);
split(x, xx - 1, x, z, 0);
rt = merge(merge(x, y), z);
}
if(op[0] == 'I')
{
k = read();
int xx = fid(id[o]);
if(k == 0) continue;//放回原位就直接跳过
if(k == 1)//上面的书多了一本
{
split(rt, xx + 1, x, y);//相当于和在平衡树中的后继互换位置
split(x, xx, x, z);
split(x, xx - 1, x, z1);
rt = merge(merge(merge(x, z), z1), y);
}
if(k == -1)//上面的书少了一本
{
split(rt, xx, x, y);//相当于和在平衡树中的前驱互换位置
split(x, xx - 1, x, z);
split(x, xx - 2, x, z1);
rt = merge(merge(merge(x, z), z1), y);
}
}
if(op[0] == 'A')
{
int xx = fid(id[o]);//查找上面有多少本书
cout << xx - 1 << endl;//因为里面的是包含自己的,所以要减去
}
if(op[0] == 'Q')
{
split(rt, o, x, y);//按照o的排名分裂
int xx = x;
while(ch[xx][1]) xx = ch[xx][1];//一直跳到最大值
cout << val[xx] << endl;
rt = merge(x, y);
}
}
return 0;
}
作者: 北烛青澜
出处:https://www.cnblogs.com/Multitree/p/17498056.html
本站使用「CC BY 4.0」创作共享协议,转载请在文章明显位置注明作者及出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!