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