洛谷P3369 普通平衡树之板子
洛谷P3369题解
传送锚点
摸鱼环节
【模板】普通平衡树
题目描述
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
- 插入一个数 \(x\)。
- 删除一个数 \(x\)(若有多个相同的数,应只删除一个)。
- 定义排名为比当前数小的数的个数 \(+1\)。查询 \(x\) 的排名。
- 查询数据结构中排名为 \(x\) 的数。
- 求 \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
- 求 \(x\) 的后继(后继定义为大于 \(x\),且最小的数)。
对于操作 3,5,6,不保证当前数据结构中存在数 \(x\)。
输入格式
第一行为 \(n\),表示操作的个数,下面 \(n\) 行每行有两个数 \(\text{opt}\) 和 \(x\),\(\text{opt}\) 表示操作的序号($ 1 \leq \text{opt} \leq 6 $)
输出格式
对于操作 \(3,4,5,6\) 每行输出一个数,表示对应答案。
样例 #1
样例输入 #1
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
样例输出 #1
106465
84185
492737
简单分析下题目,很明显可以选择用平衡树来AC他,一般来说旋转平衡树是基础,但我不会啊FHQtreep明显更有性价比,作为20世纪的好青年,当然是什么简单写什么选择顺应时代的写法,所以这题成为了FHQtreep训练鸡。
正片开始
1. 合理建树
直接选择结构体来处理树。
咱需要:
- l,r表示左子树和右子树;
- val存储权值;
- rnd存储随机权值;
- size存储当前树的大小;
- 写一个newnode函数存入新建点;
code:
struct
{
int l,r;
int val;
int rnd;
int size;
}tr[N];
int root=0,n=0;
inline int _rand(){return rnd();}
int newnode(int v)
{
tr[++n].val=v;
tr[n].rnd=_rand();
tr[n].size=1;
return n;
}
2.处理更新必要部分
将左右子树更新后加上自己,很简单,但别忘了。
code:
void pushup(int p){tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+1;}
3.核心操作之分裂合并
- 对于分裂,递归处理即可。勿忘更新。
void split(int p,int v,int &x,int &y)
{
if(!p) {x=y=0;return;}
if(tr[p].val<=v)
{
x=p;
split(tr[p].r,v,tr[p].r,y);
}
else
{
y=p;
split(tr[p].l,v,x,tr[p].l);
}
pushup(p);
}
- 对于合并操作,如果子树为空,直接相加即可。选择小根队,判断下左子树与右子树的随机权值,将较小的作为根即可。记得更新。
code:
int merge(int x,int y)
{
if(!x||!y) {return x+y;}
if(tr[x].rnd<tr[y].rnd)
{
tr[x].r=merge(tr[x].r,y);
pushup(x);
return x;
}
else
{
tr[y].l=merge(x,tr[y].l);
pushup(y);
return y;
}
}
4. 其余操作处理
太水了,分分合合就行了
- 增加操作,将树以v分成两个子树,建立一个新的插入点,可视作第三棵子树,然后将三棵子树合并即可。
code:
void insert(int v)
{
int x,y;
split(root,v,x,y);
int z=newnode(v);
root=merge(merge(x,z),y);
}
- 删除操作只需将树分成三棵子树,并将所需要删除的点的树上左子树和右子树直接合并即可。
code:
void del(int v)
{
int x,y,z;
split(root,v,x,z);
split(x,v-1,x,y);
y=merge(tr[y].l,tr[y].r);
root=merge(merge(x,y),z);
}
- 接下来的操作都可以参考上述方法,很容易得出结果。
code:
int rank(int v)
{
int x,y;
split(root,v-1,x,y);
int ans=tr[x].size+1;
root=merge(x,y);
return ans;
}
int topk(int p,int k)
{
int lsz=tr[tr[p].l].size;
if(k==lsz+1) return tr[p].val;
if(k<=lsz) return topk(tr[p].l,k);
return topk(tr[p].r,k-lsz-1);
}
int get_pre(int v)
{
int x,y;
split(root,v-1,x,y);
int ans=topk(x,tr[x].size);
root=merge(x,y);
return ans;
}
int get_suc(int v)
{
int x,y;
split(root,v,x,y);
int ans=topk(y,1);
root=merge(x,y);
return ans;
}
函数部分到此结束。
完整代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+5;
unsigned long long seed=1;
std::mt19937 rnd(std::random_device{}());
struct fhq
{
struct
{
int l,r;
int val;
int rnd;
int size;
}tr[N];
int root=0,n=0;
inline int _rand(){return rnd();}
int newnode(int v)
{
tr[++n].val=v;
tr[n].rnd=_rand();
tr[n].size=1;
return n;
}
void pushup(int p){tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+1;}
void split(int p,int v,int &x,int &y)
{
if(!p) {x=y=0;return;}
if(tr[p].val<=v)
{
x=p;
split(tr[p].r,v,tr[p].r,y);
}
else
{
y=p;
split(tr[p].l,v,x,tr[p].l);
}
pushup(p);
}
int merge(int x,int y)
{
if(!x||!y) {return x+y;}
if(tr[x].rnd<tr[y].rnd)
{
tr[x].r=merge(tr[x].r,y);
pushup(x);
return x;
}
else
{
tr[y].l=merge(x,tr[y].l);
pushup(y);
return y;
}
}
explicit fhq() { memset(tr, 0, sizeof tr); }
int size() { return tr[root].size; }
void insert(int v)
{
int x,y;
split(root,v,x,y);
int z=newnode(v);
root=merge(merge(x,z),y);
}
void del(int v)
{
int x,y,z;
split(root,v,x,z);
split(x,v-1,x,y);
y=merge(tr[y].l,tr[y].r);
root=merge(merge(x,y),z);
}
int rank(int v)
{
int x,y;
split(root,v-1,x,y);
int ans=tr[x].size+1;
root=merge(x,y);
return ans;
}
int topk(int p,int k)
{
int lsz=tr[tr[p].l].size;
if(k==lsz+1) return tr[p].val;
if(k<=lsz) return topk(tr[p].l,k);
return topk(tr[p].r,k-lsz-1);
}
int get_pre(int v)
{
int x,y;
split(root,v-1,x,y);
int ans=topk(x,tr[x].size);
root=merge(x,y);
return ans;
}
int get_suc(int v)
{
int x,y;
split(root,v,x,y);
int ans=topk(y,1);
root=merge(x,y);
return ans;
}
};
int main()
{
int n,m;
cin>>m;
fhq t;
int ans=0;
while(m--)
{
int op,x;cin>>op>>x;
if(op==1) t.insert(x);
if(op==2) t.del(x);
if(op==3) cout<<t.rank(x)<<endl;
if(op==4) cout<<t.topk(t.root,x)<<endl;
if(op==5) cout<<t.get_pre(x)<<endl;
if(op==6) cout<<t.get_suc(x)<<endl;
}
return 0;
}
双倍经验之洛谷P6136
题目简述:
【模板】普通平衡树(数据加强版)
题目背景
本题是 P3369 数据加强版,扩大数据范围并增加了强制在线。
题目的输入、输出和原题略有不同,但需要支持的操作相同。
题目描述
您需要写一种数据结构(可参考题目标题),来维护一些整数,其中需要提供以下操作:
- 插入一个整数 \(x\)。
- 删除一个整数 \(x\)(若有多个相同的数,只删除一个)。
- 查询整数 \(x\) 的排名(排名定义为比当前数小的数的个数 \(+1\))。
- 查询排名为 \(x\) 的数(如果不存在,则认为是排名小于 \(x\) 的最大数。保证 \(x\) 不会超过当前数据结构中数的总数)。
- 求 \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
- 求 \(x\) 的后继(后继定义为大于 \(x\),且最小的数)。
本题强制在线,保证所有操作合法(操作 \(2\) 保证存在至少一个 \(x\),操作 \(4,5,6\) 保证存在答案)。
输入格式
第一行两个正整数 \(n,m\),表示初始数的个数和操作的个数。
第二行 \(n\) 个整数 \(a_1,a_2,a_3,\ldots,a_n\),表示初始的数。
接下来 \(m\) 行,每行有两个整数 \(\text{opt}\) 和 \(x'\),\(\text{opt}\) 表示操作的序号($ 1 \leq \text{opt} \leq 6 \(),\)x'$ 表示加密后的操作数。
我们记 \(\text{last}\) 表示上一次 \(3,4,5,6\) 操作的答案,则每次操作的 \(x'\) 都要异或上 \(\text{last}\) 才是真实的 \(x\)。初始 \(\text{last}\) 为 \(0\)。
输出格式
输出一行一个整数,表示所有 \(3,4,5,6\) 操作的答案的异或和。
样例 #1
样例输入 #1
6 7
1 1 4 5 1 4
2 1
1 9
4 1
5 8
3 13
6 7
1 4
样例输出 #1
6
提示
样例解释
样例加密前为:
6 7
1 1 4 5 1 4
2 1
1 9
4 1
5 9
3 8
6 1
1 0
限制与约定
对于 \(100\%\) 的数据,\(1\leq n\leq 10^5\),\(1\leq m\leq 10^6\),\(0\leq a_i,x\lt 2^{30}\)。
本题输入数据较大,请使用较快的读入方式。
\(\text{upd 2022.7.22}\):新增加 \(9\) 组 Hack 数据。
思路还是一模一样,这里直接给到代码。
#include <cstdio>
#include <cstring>
#include <random>
using namespace std;
const int N=1e6+1e5+1;
unsigned long long seed=1;
std::mt19937 rnd(std::random_device{}());
struct fhq
{
struct
{
int l,r;
int val;
int rnd;
int size;
}tr[N];
int root=0,n=0;
inline int _rand(){return rnd();}
int newnode(int v)
{
tr[++n].val=v;
tr[n].rnd=_rand();
tr[n].size=1;
return n;
}
void pushup(int x){tr[x].size=tr[tr[x].l].size+tr[tr[x].r].size+1;}
void split(int rt,int v,int &x,int &y)
{
if(!rt) {x=y=0;return;}
if(tr[rt].val<=v)
{
x=rt;
split(tr[rt].r,v,tr[rt].r,y);
}
else
{
y=rt;
split(tr[rt].l,v,x,tr[rt].l);
}
pushup(rt);
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
if(tr[x].rnd<tr[y].rnd)
{
tr[x].r=merge(tr[x].r,y);
pushup(x);
return x;
}
else
{
tr[y].l=merge(x,tr[y].l);
pushup(y);
return y;
}
}
explicit fhq(){memset(tr,0,sizeof tr);}
int size(){return tr[root].size;}
void insert(int v)
{
int x,y;
split(root,v,x,y);
int z=newnode(v);
root=merge(merge(x,z),y);
}
void del(int v)
{
int x,y,z;
split(root,v,x,z);
split(x,v-1,x,y);
y=merge(tr[y].l,tr[y].r);
root=merge(merge(x,y),z);
}
int rank(int v)
{
int x,y;
split(root,v-1,x,y);
int ans=tr[x].size+1;
root=merge(x,y);
return ans;
}
int topk(int rt,int k)
{
int lsz=tr[tr[rt].l].size;
if(k==lsz+1) return tr[rt].val;
if(k<=lsz) return topk(tr[rt].l,k);
return topk(tr[rt].r,k-lsz-1);
}
int get_pre(int v)
{
int x,y;
split(root,v-1,x,y);
int ans=topk(x,tr[x].size);
root=merge(x,y);
return ans;
}
int get_suc(int v)
{
int x,y;
split(root,v,x,y);
int ans=topk(y,1);
root=merge(x,y);
return ans;
}
};
int main(void)
{
int n,m;
scanf("%d%d",&n,&m);
fhq t;int w;
for(int i=0;i<n;i++)
{
scanf("%d", &w);
t.insert(w);
}
int ans=0,last=0;
while(m--)
{
int op,x;
scanf("%d%d", &op, &x);
x^=last;
if(op==1) t.insert(x);
if(op==2) t.del(x);
if(op==3) {last=t.rank(x);ans^=last;}
if(op==4) {last=t.topk(t.root,x);ans^=last;}
if(op==5) {last=t.get_pre(x);ans^=last;}
if(op==6) {last=t.get_suc(x);ans^=last;}
}
printf("%d\n", ans);
return 0;
}