題目描述
您需要寫一種數據結構(可參考題目標題),來維護一些數,其中需要提供以下操作:
- 插入 xx 數
- 刪除 xx 數(若有多個相同的數,因只刪除一個)
- 查詢 xx 數的排名(排名定義爲比當前數小的數的個數 +1+1 。若有多個相同的數,因輸出最小的排名)
- 查詢排名爲 xx 的數
- 求 xx 的前驅(前驅定義爲小於 xx ,且最大的數)
- 求 xx 的後繼(後繼定義爲大於 xx ,且最小的數)
輸入輸出格式
輸入格式:
第一行爲 nn ,表示操作的個數,下面 nn 行每行有兩個數 optopt 和 xx , optopt 表示操作的序號( 1 \leq opt \leq 61≤opt≤6 )
輸出格式:
對於操作 3,4,5,63,4,5,6 每行輸出一個數,表示對應答案
輸入輸出樣例
輸入樣例#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
說明
時空限制:1000ms,128M
1.n的數據範圍: n \leq 100000n≤100000
2.每個數的數據範圍: [-{10}^7, {10}^7][−107,107]
學習了下伸展樹,伸展樹的功能真的很強大,直接粘板子就行,板子裏的註釋寫的挺詳細了
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
struct tree
{
int father,lch,rch,val,cnt,size; //父節點、左兒子、右兒子、值、該值出現次數、子樹大小
}stree[1000005];
int root,tot; // 根節點,樹的大小
inline void clearr(int x)
{
stree[x].lch = stree[x].rch = stree[x].father = stree[x].size = stree[x].cnt = stree[x].val = 0;
}
inline bool get(int x) // 判斷是父親的左兒子還是右兒子
{
return stree[ stree[x].father ].rch == x;
}
inline void update(int x) // 更新子樹大小
{
if (x)
{
stree[x].size = stree[x].cnt; // 該值出現次數 + 左右子樹大小
if (stree[x].lch)
stree[x].size += stree[ stree[x].lch ].size;
if (stree[x].rch)
stree[x].size += stree[ stree[x].rch ].size;
}
}
inline void rotate(int x)//旋轉
{
int fa,oldf,whichx;
fa = stree[x].father; // x的父節點
oldf = stree[fa].father; // x父節點的父節點
whichx = get(x); // 判斷x是父節點左兒子還是右兒子
if (whichx) //x是父節點的右兒子
{
stree[fa].rch = stree[x].lch;
stree[ stree[fa].rch ].father = fa; // 將x的左兒子變爲x父節點的右兒子
stree[x].lch = fa;
stree[fa].father = x;
stree[x].father = oldf; // 將x父節點變爲x左兒子
}
else // 同上
{
stree[fa].lch = stree[x].rch;
stree[ stree[fa].lch ].father = fa;
stree[x].rch = fa;
stree[fa].father = x;
stree[x].father = oldf;
}
if (oldf) // 如果有x的父節點有父節點,則更新祖父節點的(左/右)兒子爲x
{
if (stree[oldf].rch == fa)
stree[oldf].rch = x;
else
stree[oldf].lch = x;
}
update(fa);
update(x); // 更新x與x父節點的大小
}
inline void splay(int x, int t)//將x伸展到t的子節點
{
int fa = stree[x].father; //將x伸展至父節點
while (fa != t)
{
if (stree[fa].father) // 如果父節點有父節點
{
if (get(x) == get(fa)) // 一字型(需旋轉父節點再旋轉x)
rotate(fa);
else
rotate(x); // 之字形(需旋轉x兩次)
}
rotate(x); // 旋轉x
fa = stree[x].father; //更新父節點
}
if (!t)
root = x; // 更新根節點
}
inline void insert(int x) // 插入x
{
if (!root) // 如果splay樹爲空
{
tot++;
stree[tot].lch = stree[tot].rch = stree[tot].father = 0;
root = tot;
stree[tot].size = stree[tot].cnt = 1;
stree[tot].val = x;
return;
}
int u = root;
int fa = 0;
while (1)
{
if (x == stree[u].val) // 如果splay樹裏已經有這個值
{
stree[u].cnt++;
update(u);
update(fa);
splay(u, 0);
return;
}
fa = u;
if (stree[u].val < x)
u = stree[u].rch;
else
u = stree[u].lch; // 找該值應該是在當前節點的左/右子樹內
if (!u) // 如果找到x應作爲fa節點的左/右兒子
{
tot++; // 樹大小+1
stree[tot].lch = stree[tot].rch = 0;
stree[tot].father = fa;
stree[tot].size = stree[tot].cnt = 1;
if (stree[fa].val < x)
stree[fa].rch = tot;
else
stree[fa].lch = tot;
stree[tot].val = x; // 創建該節點
update(fa); // 更新父節點大小
splay(tot, 0); // 將當前節點伸展到根
return;
}
}
}
int find(int x) //查找x爲第幾小的值
{
int ans = 0;
int u = root;
while (1)
{
if (x < stree[u].val) // 如果x在當前節點的左子樹內
u = stree[u].lch;
else // 如果x在當前節點的右子樹內
{
if (stree[u].lch) //當前節點的左子樹都比x小
ans += stree[ stree[u].lch ].size;
if (x == stree[u].val) // 如果x是當前節點,則找到排名
{
splay(u, 0); // 將x伸展到根節點
return ans + 1;
}
ans += stree[u].cnt; // 當前節點比x小
u = stree[u].rch; // 更新當前節點
}
}
}
int findx(int x)//找第x小的點
{
int u = root;
while (1)
{
if (stree[u].lch && stree[ stree[u].lch ].size >= x) // 如果當前節點的左子樹大小已經大於等於x,即就在當前節點的左子樹內找
u = stree[u].lch;
else
{
int tmp = stree[u].cnt;
if (stree[u].lch)
tmp += stree[ stree[u].lch ].size;
if (tmp >= x) // 當前值即爲要尋找的值
return stree[u].val;
x -= tmp; // 已經找到最小的tmp個樹
u = stree[u].rch; // 去當前節點的有子樹內找
}
}
}
inline int pre()//查找x的前驅(需先插入x,查找完後刪除,返回值爲前驅的下標)
{
int now = stree[root].lch; //插入x時,x已經作爲樹的根,在x的左子樹內找最大值
while (stree[now].rch)
now = stree[now].rch;
return now;
}
inline int next()//查找x的後繼(需先插入x,查找完後刪除,返回值爲後繼的下標)
{
int now = stree[root].rch; //插入x時,x已經作爲樹的根,在x的右子樹內找最小值
while (stree[now].lch)
now = stree[now].lch;
return now;
}
void deletee(int x) // 刪除x
{
int p = find(x); // 此時x已經成爲根節點
if (stree[root].cnt > 1) // 如果x出現次數超過一次
{
stree[root].cnt--;
update(root);
return;
}
if (!stree[root].lch && !stree[root].rch) // 如果x沒有左右子樹,直接刪除即可
{
clearr(root);
root = 0;
return;
}
if (!stree[root].lch) //如果x沒有左子樹,則將x的右兒子作爲根,直接刪除x
{
int tmp = root;
root = stree[root].rch;
stree[root].father = 0;
clearr(tmp);
return;
}
if (!stree[root].rch)//如果x沒有右子樹,則將x的左兒子作爲根,直接刪除x
{
int tmp = root;
root = stree[root].lch;
stree[root].father = 0;
clearr(tmp);
return;
}
int left = pre(); // 找x的前驅
int tmp = root;
splay(left, 0); // 將x的前驅伸展到根節點
stree[root].rch = stree[tmp].rch;
stree[ stree[tmp].rch ].father = root; // x的右兒子作爲x前驅的右兒子
clearr(tmp); // 刪除x節點
update(root); // 更新樹大小
return;
}
int main()
{
// freopen("in.txt", "r", stdin);
int n;
scanf("%d", &n);
while (n--)
{
int f,x;
scanf("%d%d", &f, &x);
if (f == 1)
insert(x);
if (f == 2)
deletee(x);
if (f == 3)
printf("%d\n", find(x));
if (f == 4)
printf("%d\n", findx(x));
if (f == 5)
{
insert(x);
printf("%d\n", stree[pre()].val);
deletee(x);
}
if (f == 6)
{
insert(x);
printf("%d\n", stree[next()].val);
deletee(x);
}
}
return 0;
}