洛谷P3369 普通平衡樹(Splay)
題目:點選開啟連結
題意:中文題,不解釋。
分析:這題是平衡樹操作的裸題,用treap或者splay都行,我這裡用的是splay。splay入門推薦https://blog.csdn.net/clove_unique/article/details/50630280,寫的非常詳細。不知道為啥find的時候一定要翻轉,不翻轉就會wa,歡迎大佬留言指教。
程式碼:
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize(4)
#pragma comment(linker, "/STACK:102400000,102400000")
#include<unordered_map>
#include<unordered_set>
#include<algorithm>
#include<iostream>
#include<fstream>
#include<complex>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<iomanip>
#include<string>
#include<cstdio>
#include<bitset>
#include<vector>
#include<cctype>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<set>
#include<map>
using namespace std;
#define pt(a) cout<<a<<endl
#define debug test
#define mst(ss,b) memset((ss),(b),sizeof(ss))
#define rep(i,a,n) for (int i=a;i<=n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
#define ll long long
#define ull unsigned long long
#define pb push_back
#define mp make_pair
#define inf 0x3f3f3f3f
#define eps 1e-10
#define PI acos(-1.0)
typedef pair<int,int> PII;
const ll mod = 1e9+7;
const int N = 1e6+10;
ll gcd(ll p,ll q){return q==0?p:gcd(q,p%q);}
ll qp(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
int to[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
int ch[N][2],f[N],size[N],cnt[N],key[N];
int sz,root;
void clear(int x) {///清空節點
ch[x][0]=ch[x][1]=f[x]=size[x]=cnt[x]=key[x]=0;
}
bool get(int x) {///判斷左右兒子
return ch[f[x]][1]==x;
}
void update(int x) {///更新包括x的子樹大小
if(x) {
size[x]=cnt[x];
if(ch[x][0]) size[x]+=size[ch[x][0]];
if(ch[x][1]) size[x]+=size[ch[x][1]];
}
}
void rotate(int x) {///旋轉
int old=f[x],oldf=f[old],whichx=get(x);///找到父親和祖先 判斷左右兒子
ch[old][whichx]=ch[x][whichx^1]; f[ch[old][whichx]]=old;///三步走之一
ch[x][whichx^1]=old;f[old]=x;///三步走之二
f[x]=oldf;///三步走之三
if(oldf) ch[oldf][ch[oldf][1]==old]=x;
update(old); update(x);
}
void splay(int x) {///伸展 即多次旋轉 直到根
for(int fa;fa=f[x];rotate(x))
if(f[fa]&&(get(x)==get(fa))) rotate(fa);///如果兒子 父親 祖父一條線 先旋轉父親,否則會形成單旋使平衡樹失衡
root=x;
}
void insert(int x) {///插入x
if(root==0) { sz++; ch[sz][0]=ch[sz][1]=f[sz]=0; root=sz; size[sz]=cnt[sz]=1; key[sz]=x; return; }///空樹特判
int now=root,fa=0;
while(1) {
if(x==key[now]) {///結點的關鍵字等於當前要插入的點
cnt[now]++;///now結點的關鍵字出現的次數(權值)+1
update(now);///更新當前節點的子樹節點個數
update(fa);///更新父親節點的子樹節點個數
splay(now);///伸展 把now改為root
break;
}
fa=now;///向下查詢左右子樹
now=ch[now][key[now]<x];
if(now==0) {///如果已經到了最底下了,那麼就可以直接插入
sz++;///更新相關資訊
ch[sz][0]=ch[sz][1]=0;
f[sz]=fa;
size[sz]=cnt[sz]=1;
ch[fa][key[fa]<x]=sz;
key[sz]=x;
update(fa);
splay(sz);
break;
}
}
}
int find(int x) {///查詢x的排名
int now=root,ans=0;
while(1) {
if(x<key[now]) now=ch[now][0];///如果x比當前結點小,即應該向左子樹尋找,ans不用改變
else {
ans += (ch[now][0]?size[ch[now][0]]:0);///如果x比當前結點大,即應該向右子樹尋找,ans需要加上左子樹的大小以及根的大小
if(x==key[now]) {///不要忘記了再splay一下
splay(now); return ans+1;
}
ans += cnt[now];
now = ch[now][1];
}
}
}
int findx(int x) {///找到排名為x的點
int now=root;
while(1) {
if(ch[now][0]&&x<=size[ch[now][0]]) now=ch[now][0];///如果當前點有左子樹,並且x比左子樹的大小小的話,即向左子樹尋找
else {
///否則,向右子樹尋找:先判斷是否有右子樹,然後記錄右子樹的大小以及當前點的大小(都為權值),
///用於判斷是否需要繼續向右子樹尋找。
int temp=(ch[now][0]?size[ch[now][0]]:0)+cnt[now];
if(x<=temp) return key[now];
x-=temp; now = ch[now][1];
}
}
}
int pre() {///求x的前驅 前驅定義為小於x,且最大的數
int now=ch[root][0];///求x的前驅其實就是求x的左子樹的最右邊的一個結點
while(ch[now][1]) now=ch[now][1];
return now;
}
int next() {///求x的後繼 後繼定義為大於x,且最小的數
int now=ch[root][1];///後繼是求x的右子樹的左邊一個結點
while(ch[now][0]) now=ch[now][0];
return now;
}
void del(int x) {///刪除操作
int whatever=find(x);///隨便find一下x。目的是:將x旋轉到根。
if(cnt[root]>1) { cnt[root]--; update(root); return; }///如果cnt[root]>1,即不只有一個x的話,直接-1返回。
if(!ch[root][0]&&!ch[root][1]) { clear(root); root=0; return; }///如果root並沒有孩子,就說名樹上只有一個x而已,直接clear返回。
if(!ch[root][0]) {///如果root只有右兒子,那麼直接clear root,然後把唯一的兒子當作根就可以了(f賦0,root賦為唯一的兒子)
int oldroot=root; root=ch[root][1]; f[root]=0; clear(oldroot); return;
}else if(!ch[root][1]) {///如果root只有左兒子,那麼直接clear root,然後把唯一的兒子當作根就可以了(f賦0,root賦為唯一的兒子)
int oldroot=root; root=ch[root][0]; f[root]=0; clear(oldroot); return;
}
int leftbig=pre(),oldroot=root;///我們找到新根,也就是x的前驅(x左子樹最大的一個點),將它旋轉到根。
splay(leftbig);
ch[root][1]=ch[oldroot][1];///然後將原來x的右子樹接到新根的右子樹上(注意這個操作需要改變父子關係)。這實際上就把x刪除了。
f[ch[oldroot][1]]=root;
clear(oldroot);///清空原根
update(root);///不要忘了update新根。
}
int main() {
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n,opt,x;
cin>>n;
rep(i,1,n) {
cin>>opt>>x;
if(opt==1) insert(x);
else if(opt==2) del(x);
else if(opt==3) cout<<find(x)<<endl;
else if(opt==4) cout<<findx(x)<<endl;
else if(opt==5) insert(x),cout<<key[pre()]<<endl,del(x);///問題可以轉化為將x插入,求出樹上的前驅(後繼),再將x刪除的問題。
else if(opt==6) insert(x),cout<<key[next()]<<endl,del(x);
}
return 0;
}
相關文章
- 洛谷P3369 普通平衡樹之板子
- P3369 【模板】普通平衡樹(treap)
- P3369 【模板】普通平衡樹 —— Treap FHQtreapQT
- FHQ-Treap P3369 【模板】普通平衡樹
- 普通平衡樹學習筆記之Splay演算法筆記演算法
- P3391 【模板】文藝平衡樹(Splay)
- BZOJ 3196 Tyvj 1730 二逼平衡樹:線段樹套splay
- [學習筆記] Splay & Treap 平衡樹 - 資料結構筆記資料結構
- 洛谷-P1250 種樹
- 洛谷P1087 FBI樹
- 洛谷P3285 [SCOI2014]方伯伯的OJ 動態開點平衡樹
- 【線段樹提高】51nod &&洛谷
- 洛谷P4197 Peaks(Kruskal重構樹 主席樹)
- 洛谷 P4913 二叉樹深度二叉樹
- 洛谷 P5350 序列 珂朵莉樹
- 洛谷
- 洛谷題單指南-二叉樹-P5076 【深基16.例7】普通二叉樹(簡化版)二叉樹
- P6136 【模板】普通平衡樹(資料加強版)
- 二叉樹 遞迴 洛谷P1364二叉樹遞迴
- 洛谷題單指南-線段樹-P1471 方差
- 可持久化線段————主席樹(洛谷p3834)持久化
- 洛谷 P2590 [ZJOI2008]樹的統計
- 伸展樹(Splay)學習筆記筆記
- 洛谷題單指南-線段樹-P3373 【模板】線段樹 2
- 平衡樹
- 洛谷團隊
- 洛谷 - P3690 【模板】Link Cut Tree (動態樹)(LCT模板)
- 洛谷 P6362 平面歐幾里得最小生成樹
- 洛谷P4425 [HNOI/AHOI2018]轉盤(線段樹)
- 洛谷 P3919 可持久化線段樹 1 之主席樹模板(初級)持久化
- 洛谷死亡時間
- 洛谷P1786
- 洛谷 - P5369
- 洛谷P6786
- 洛谷P10725
- 洛谷P10693
- 洛谷 - P6190
- 洛谷——玩具謎題