樹套樹(線段樹套Splay) 模板 + 詳解

(退役的我又詐屍了)

又是一個毒瘤東西 =-=

當初看不懂概念於是沒管 上個月看見某日報上講了下發現莫名其妙地看明白了

於是就照着概念自己又摸了下來 於是差不多成型了

然後通過 @千年之狐_天才 的幫助 調了調細節(改權值空樹ins炸掉了然後改成先ins再del這個一定要記得啊qwq)

好了開講了

 

前置知識 Splay(對是我的) + 線段樹 + 由於詢問和主席樹差不多 最好了解一下?(我的主席樹要看好久爲了不浪費dalao們的時間還是不放了)

 

概念?這個日報還是不錯的 有配圖 然後我再來口胡一通

首先我們需要思考題目 如何維護這道題目的五個操作

第 k 大 改權值 前驅後繼 隨便來個平衡樹就可以了嘛 然而 "區間" 二字........

於是線段樹就出來了 想想看你一個個枚舉不如用線段樹部分部分記是不是?

再然後線段樹貌似存不了平衡樹要求的大部分信息啊.......怎麼辦?

於是線段樹每個節點糊個平衡樹上去 平衡樹我只會 Splay 那我們就用 Splay 吧(喂喂好不負責啊)

爲什麼這樣是可以的呢?因爲原來的平衡樹上的操作是可以通過合併區間得到的 詳細實現見上面日報

哦不 操作我要講的 別搶了我飯碗

 

好了開始建樹了

話說我們有了序列 [1,n] 後 我們像線段樹一樣 build 一個線段樹出來 然後怎麼弄Splay進去呢

我們線段樹大概是這樣建的

inline void build(int l,int r,int len) {
	if (l == r) return seg[len].v = v[l];
	int mid = (l + r) >> 1;
	build(l,mid,len << 1);
	build(mid + 1,r,len << 1 | 1);
	seg[len].sum = seg[len << 1].sum + seg[len << 1 | 1].sum;
	seg[len].mx = max(seg[len << 1].mx,seg[len << 1 | 1].mx);
}

在樹套樹裏面我們主要是用各個 Splay 的 於是線段樹上不用存什麼東西 但是每個節點要保證連接到該節點的 Splay 上

主要是因爲 Splay 大小都不一樣 爲了省空間只能鄰接 像主席樹裏面存各個edition一樣

於是我們就....把上面求和求 max 存權什麼的都咔擦掉就好啦 當然要加個 ins

原本大概是這樣子的 ( root 數組就是所謂存每個線段樹上掛的 Splay 的根的位置 )

inline void build(int l,int r,int len) {
	root[len] = ++tot;
	for (int p = l ; p <= r ; ++ p) ins(len,v[p]);
	if (l == r) return;
	int mid = (l + r) >> 1;
	build(l,mid,len << 1);
	build(mid + 1,r,len << 1 | 1);
}

但是爲了讓 ins 裏面不判斷沒根情況 於是我改成了這個毒瘤東西

inline void build(int l,int r,int len) {
	root[len] = ++tot;
	e[tot].siz = 1;
	e[tot].tie = 1;
	e[tot].v = v[r]; //這個單個點也算進去了 順便處理了無根情況
	if (l == r) return;
	int mid = (l + r) >> 1;
	build(l,mid,len << 1);
	build(mid + 1,r,len << 1 | 1);
	for (int p = l ; p < r ; ++ p) ins(len,v[p]); //這裏少個r就好啦
}

於是這就是我在修改權值先 del 後樹空然後 ins 進空樹炸掉的原因 改到崩潰

然後 ins 和 del 順便也放出來了吧 這裏 ins 少了無根情況 del 也少了無根情況

既然這兩個都放了 裏面用到的 splay,rotate,find 也放出來吧 這些都是Splay的基本操作 就不多加闡述了

不會?戳進去學啊

 

然後因爲各個不同的 Splay 查值都要用同一個 find 函數 於是原來的 root 換成 root[len]

還有 splay 函數裏面因爲有換根操作 所以在外面不能像 find 一樣代個 root[len] 進去

這些細節一定要非常清楚啊 代錯了要找好久的

rotate 裏面幫我改題的 dalao 說更新 x 可以放 splay 函數裏面 快上許多 想想還真是的

add 函數是我爲了代碼能壓行自己加的 如果不想用可以 ins 函數裏面自己糊上

inline void rotate(int x) {
	int y = e[x].fa,z = e[y].fa,mode = e[y].son[0] == x;
	e[z].son[e[z].son[1] == y] = x;
	e[x].fa = z;
	e[y].son[mode ^ 1] = e[x].son[mode];
	e[e[x].son[mode]].fa = y;
	e[x].son[mode] = y;
	e[y].fa = x;
	e[y].siz = e[e[y].son[0]].siz + e[e[y].son[1]].siz + e[y].tie;
}
inline void splay(int rt,int x) {
	while (e[x].fa) {
		int y = e[x].fa,z = e[y].fa;
		if (z) {
			(e[y].son[1]==x)^(e[z].son[1]==y)?rotate(x):rotate(y);
		} rotate(x);
	} root[rt] = x;
	e[x].siz = e[e[x].son[0]].siz + e[e[x].son[1]].siz + e[x].tie;
}
inline int find(int now,int w) {
	while (e[now].v != w)
		if (e[now].v > w) {
			if (e[now].son[0]) now = e[now].son[0];
			else break;
		} else {
			if (e[now].son[1]) now = e[now].son[1];
			else break;
		}
	return now;
}
inline void add(int f,int w) {
	e[++tot].fa = f;
	e[tot].siz = 1;
	e[tot].tie = 1;
	e[tot].v = w;
}
inline void ins(int rt,int p) {
	int pos = find(root[rt],p);
	if (e[pos].v == p) ++e[pos].tie; else
	add(pos,p),e[pos].son[e[pos].v < p] = tot;
	for (int now = pos ; now ; ++e[now].siz,now = e[now].fa);
	splay(rt,e[pos].v == p ? pos : tot);
}
inline void del(int len,int p) {
	int pos = find(root[len],p); splay(len,pos);
	if (e[pos].tie > 1) {--e[pos].tie,--e[pos].siz; return;}
	if (!e[pos].son[0])
		e[e[pos].son[1]].fa = 0,
		root[len] = e[pos].son[1];
    else {
		e[e[pos].son[0]].fa = 0;
		int lax = find(e[pos].son[0],INF);
		splay(len,lax);
		int rt = root[len];
		e[rt].siz += e[e[pos].son[1]].siz;
		e[rt].son[1] = e[pos].son[1];
		e[e[pos].son[1]].fa = rt;
	}
	e[pos].son[0] = 0;
	e[pos].son[1] = 0;
	e[pos].siz = 0;
	e[pos].tie = 0;
	e[pos].fa = 0;
	e[pos].v = 0;
}

 

於是建樹差不多完了 順便修改操作的板子也打好了 那麼我們先看一下修改操作:

Case 3:修改某一位置上的數值

因爲是單點修改 而且涉及到多個線段樹上的點 所以很乾脆的不用懶標記 並且沿樹下去一個個修改

你想想嘛 你要修改第 2 個 序列總長是 [1,8] 那除了他 [1,4] , [1,2] , [2,2] 你也要修改嘛 很多個 Splay 掛在線段樹上的誒

於是下面直接弄代碼了

 

主程序中:有個改權值 然後 lim 是什麼在 case 2 裏會講到

case 3:update(1,n,1,i,j),v[i] = j,lim = max(lim,v[i]);break;

因爲無根情況 在 update 的時候 先 ins 再 del 就是這個坑 丟了90分(好押韻啊QvQ)

inline void update(int l,int r,int len,int i,int w) {
	ins(len,w),del(len,v[i]);
	if (l == r) return;
	int mid = (l + r) >> 1;
	if (i <= mid) update(l,mid,len << 1,i,w);
	else update(mid + 1,r,len << 1 | 1,i,w);
}

和當初建樹差不多有木有 不多講了

 

好了接下來按操作順序解決

 

Case 1:查詢k在區間內的排名

主程序內:

case 1:k = re(),printf("%d\n",rank(1,n,1,i,j,k) + 1);break;

加一是因爲找排名的時候是找比他小的 然後加1就到他了

主要是查case 1的時候數可能不在當前區間內(甚至總區間都沒有) 於是就成這樣了

話說這裏面因爲是找區間的 所以在[1,n]的大樹上找顯然不行 於是就往下找到完全符合的區間再合併

所以函數 rank 是跑線段樹 然後再根據遍歷到的線段樹的點的 root[len] 代進 rnk 函數裏面

rnk 函數就是splay裏面的找當前權值排位的啦 不多說了

對了 case 1 和 case 2 的 rank 和 rnk 函數都是共用的 因爲我太懶了......

好了 下面放代碼了

inline int rnk(int rt,int p) {
	int pos = find(root[rt],p); splay(rt,pos);
	return e[e[pos].son[0]].siz+(e[pos].v<p?e[pos].tie:0);
}
inline int rank(int l,int r,int len,int i,int j,int k) {
	if (i <= l && r <= j) return rnk(len,k);
	int mid = (l + r) >> 1,ans = 0;
	if (i <= mid) ans = rank(l,mid,len << 1,i,j,k);
	if (mid < j) ans += rank(mid + 1,r,len << 1 | 1,i,j,k);
	return ans;
}

 

Case 2:查詢區間內排名爲k的值

這個操作特別噁心 我們那麼多個區間並上去 怎麼找嘛

dalao們提供了一種十分優秀的方法——二分序列最大值每次都判當前 l 和 r 的 平均數的排名

如果大於等於了說明滿足條件 r 就掉到 mid

如果小於了說明不滿足條件 l 就變成 mid + 1

這樣還能確保找到的數最小呢 就是不會出現返回的數不在序列裏面

因爲答案更新必定是因爲區間內有的數嘛 然後更新時我們跳到 mid 那 mid 肯定在區間內

所以大於等於直接跳到 mid 小於就跨過 mid

很清楚了 再補充一點 序列最大值是 [1,n] 的最大值 這個可以在讀入的時候處理出來

然後 case 3 的時候改權值順便更新一下(然而我沒更新交上去還是過了 剛剛纔想起來)

那麼下放代碼

inline int tkth(int i,int j,int k) {
	int mx = lim;
	for (int rk,mn=0,mi=(mn+mx)>>1;mn!=mx;mi=(mn+mx)>>1)
	rk=rank(1,n,1,i,j,mi+1),rk<k?mn=mi+1:mx=mi;
	return mx;
}

 

Case 4&5:查詢k在區間內的前驅/後繼

(前驅定義爲嚴格小於x,且最大的數,若不存在輸出-2147483647)

(後繼定義爲嚴格大於x,且最小的數,若不存在輸出2147483647)

主程序內:

case 4:k = re(),printf("%d\n",fpre(1,n,1,i,j,k));break;
case 5:k = re(),printf("%d\n",fsuc(1,n,1,i,j,k));break;

查前驅後繼也是分兩部分 先跑進線段樹的 然後再進入 Splay 的

線段樹 的裏面就放一個變量 bottle 存 然後左右兩兒子的答案取最值再並上去

bottle 一開始是 0x7fffffff 或者 -0x7fffffff 的 因爲題目要求沒前驅/後繼就輸出這個 而且沒前驅/後繼的時候返回答案也不怕被更新

注意找前驅不能大於等於那個數 所以有兩個 if 判斷

Splay 的裏面就直接是 Splay 的 找前驅後繼了(不會?看我的Splay去)

(剛剛不小心把我小號拿去交了一遍這個題...不是黑的真是尷尬了)

所以下放代碼

inline int pre(int rt,int p) {
	int pos = find(root[rt],p); splay(rt,pos);
	return e[pos].v<p?e[pos].v:e[pos].son[0]?e[find(e[pos].son[0],INF)].v:-INF;
}
inline int fpre(int l,int r,int len,int i,int j,int k) {
	if (i <= l && r <= j) return pre(len,k);
	int mid = (l + r) >> 1,ans = -INF,bot = -INF;
	if (i <= mid) bot = fpre(l,mid,len << 1,i,j,k);
	if (bot > ans && bot < k) ans = bot;
	if (mid < j) bot = fpre(mid + 1,r,len << 1 | 1,i,j,k);
	if (bot > ans && bot < k) ans = bot;
	return ans;
}
inline int suc(int rt,int p) {
	int pos = find(root[rt],p); splay(rt,pos);
	return e[pos].v>p?e[pos].v:e[pos].son[1]?e[find(e[pos].son[1],0)].v:INF;
}
inline int fsuc(int l,int r,int len,int i,int j,int k) {
	if (i <= l && r <= j) return suc(len,k);
	int mid = (l + r) >> 1,ans = INF,bot=INF;
	if (i <= mid) bot = fsuc(l,mid,len << 1,i,j,k);
	if (bot < ans && bot > k) ans = bot;
	if (mid < j) bot = fsuc(mid + 1,r,len << 1 | 1,i,j,k);
	if (bot < ans && bot > k) ans = bot;
	return ans;
}

 

 

 

好了下面是總代碼 然後題目鏈接這裏再放一個吧

挺長的呢 不過已經完了(哎爲了打這個多留了半小時 今天飯堂都差不多沒菜了)

#include <algorithm>
#include <cstring>
#include <cstdio>
#define N 50010
#define INF 0x7fffffff
inline int re() {
	int x = 0,y = 0; char q = getchar();
	while (q < '0' && q != '-' || q > '9') q = getchar();
	if (q == '-') ++ y,q = getchar();
	while ('0' <= q && q <= '9') x = x * 10 + q - 48,q = getchar();
	return y ? -x : x;
}
struct splay{int fa,son[2],siz,tie,v;}e[N << 6];
int tr[N << 2],root[N << 2],v[N],tot,lim,n = re(),m = re();
inline int max(int x,int y) {return x > y ? x : y;}
inline int min(int x,int y) {return x < y ? x : y;}
inline void pushup(int x){}
inline void rotate(int x) {
	int y = e[x].fa,z = e[y].fa,mode = e[y].son[0] == x;
	e[z].son[e[z].son[1] == y] = x;
	e[x].fa = z;
	e[y].son[mode ^ 1] = e[x].son[mode];
	e[e[x].son[mode]].fa = y;
	e[x].son[mode] = y;
	e[y].fa = x;
	e[y].siz = e[e[y].son[0]].siz + e[e[y].son[1]].siz + e[y].tie;
}
inline void splay(int rt,int x) {
	while (e[x].fa) {
		int y = e[x].fa,z = e[y].fa;
		if (z) {
			(e[y].son[1]==x)^(e[z].son[1]==y)?rotate(x):rotate(y);
		} rotate(x);
	} root[rt] = x;
	e[x].siz = e[e[x].son[0]].siz + e[e[x].son[1]].siz + e[x].tie;
}
inline int find(int now,int w) {
	while (e[now].v != w)
		if (e[now].v > w) {
			if (e[now].son[0]) now = e[now].son[0];
			else break;
		} else {
			if (e[now].son[1]) now = e[now].son[1];
			else break;
		}
	return now;
}
inline void add(int f,int w) {
	e[++tot].fa = f;
	e[tot].siz = 1;
	e[tot].tie = 1;
	e[tot].v = w;
}
inline void ins(int rt,int p) {
	int pos = find(root[rt],p);
	if (e[pos].v == p) ++e[pos].tie; else
	add(pos,p),e[pos].son[e[pos].v < p] = tot;
	for (int now = pos ; now ; ++e[now].siz,now = e[now].fa);
	splay(rt,e[pos].v == p ? pos : tot);
}
inline void del(int len,int p) {
	int pos = find(root[len],p); splay(len,pos);
	if (e[pos].tie > 1) {--e[pos].tie,--e[pos].siz; return;}
	if (!e[pos].son[0])
		e[e[pos].son[1]].fa = 0,
		root[len] = e[pos].son[1];
    else {
		e[e[pos].son[0]].fa = 0;
		int lax = find(e[pos].son[0],INF);
		splay(len,lax);
		int rt = root[len];
		e[rt].siz += e[e[pos].son[1]].siz;
		e[rt].son[1] = e[pos].son[1];
		e[e[pos].son[1]].fa = rt;
	}
	e[pos].son[0] = 0;
	e[pos].son[1] = 0;
	e[pos].siz = 0;
	e[pos].tie = 0;
	e[pos].fa = 0;
	e[pos].v = 0;
}
inline void build(int l,int r,int len) {
	root[len] = ++tot;
	e[tot].siz = 1;
	e[tot].tie = 1;
	e[tot].v = v[r];
	if (l == r) return;
	int mid = (l + r) >> 1;
	build(l,mid,len << 1);
	build(mid + 1,r,len << 1 | 1);
	for (int p = l ; p < r ; ++ p) ins(len,v[p]);
}
inline int rnk(int rt,int p) {
	int pos = find(root[rt],p); splay(rt,pos);
	return e[e[pos].son[0]].siz+(e[pos].v<p?e[pos].tie:0);
}
inline int rank(int l,int r,int len,int i,int j,int k) {
	if (i <= l && r <= j) return rnk(len,k);
	int mid = (l + r) >> 1,ans = 0;
	if (i <= mid) ans = rank(l,mid,len << 1,i,j,k);
	if (mid < j) ans += rank(mid + 1,r,len << 1 | 1,i,j,k);
	return ans;
}
inline int tkth(int i,int j,int k) {
	int mx = lim;
	for (int rk,mn=0,mi=(mn+mx)>>1;mn!=mx;mi=(mn+mx)>>1)
	rk=rank(1,n,1,i,j,mi+1),rk<k?mn=mi+1:mx=mi;
	return mx;
}
inline void update(int l,int r,int len,int i,int w) {
	ins(len,w),del(len,v[i]);
	if (l == r) return;
	int mid = (l + r) >> 1;
	if (i <= mid) update(l,mid,len << 1,i,w);
	else update(mid + 1,r,len << 1 | 1,i,w);
}
inline int pre(int rt,int p) {
	int pos = find(root[rt],p); splay(rt,pos);
	return e[pos].v<p?e[pos].v:e[pos].son[0]?e[find(e[pos].son[0],INF)].v:-INF;
}
inline int fpre(int l,int r,int len,int i,int j,int k) {
	if (i <= l && r <= j) return pre(len,k);
	int mid = (l + r) >> 1,ans = -INF,bot = -INF;
	if (i <= mid) bot = fpre(l,mid,len << 1,i,j,k);
	if (bot > ans && bot < k) ans = bot;
	if (mid < j) bot = fpre(mid + 1,r,len << 1 | 1,i,j,k);
	if (bot > ans && bot < k) ans = bot;
	return ans;
}
inline int suc(int rt,int p) {
	int pos = find(root[rt],p); splay(rt,pos);
	return e[pos].v>p?e[pos].v:e[pos].son[1]?e[find(e[pos].son[1],0)].v:INF;
}
inline int fsuc(int l,int r,int len,int i,int j,int k) {
	if (i <= l && r <= j) return suc(len,k);
	int mid = (l + r) >> 1,ans = INF,bot=INF;
	if (i <= mid) bot = fsuc(l,mid,len << 1,i,j,k);
	if (bot < ans && bot > k) ans = bot;
	if (mid < j) bot = fsuc(mid + 1,r,len << 1 | 1,i,j,k);
	if (bot < ans && bot > k) ans = bot;
	return ans;
}
int main() {
	for (int a = 1 ; a <= n ; ++ a) lim = max(lim,v[a] = re());
	build(1,n,1);
	for (int op,i,j,k ; m ; -- m) {
		op = re(),i = re(),j = re();
		switch (op) {
			case 1:k = re(),printf("%d\n",rank(1,n,1,i,j,k) + 1);break;
			case 2:k = re(),printf("%d\n",tkth(i,j,k));break;
			case 3:update(1,n,1,i,j),v[i] = j,lim = max(lim,v[i]);break;
			case 4:k = re(),printf("%d\n",fpre(1,n,1,i,j,k));break;
			case 5:k = re(),printf("%d\n",fsuc(1,n,1,i,j,k));break;
		}
	}
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章