洛谷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;
}