1. 程式人生 > 實用技巧 >【BZOJ4825】[HNOI2017] 單旋(樹狀陣列)

【BZOJ4825】[HNOI2017] 單旋(樹狀陣列)

點此看題面

大致題意: 定義"單旋Splay"為\(Spaly\),對一棵\(Spaly\)進行插入、\(Spaly\)最值、\(Spaly\)並刪除最值操作,求每次操作的複雜度(操作節點深度)。

找規律

這題乍一看似乎根本不可做,但要注意,題目中需要\(Spaly\)的物件僅僅是最值!

顯然,最小值就是不斷\(Zig\),最大值就是不斷\(Zag\)

然後找一找規律,就會發現,以最小值為例,實際上就是它的右兒子佔據了它原先的位置(變成了它父節點的左兒子),然後原先的根節點變成了它的新右兒子。

也就是說,它自身深度變成\(1\),子樹內的點深度不變,其餘點深度加\(1\)

如果還要刪除最值,就是子樹內的點深度減\(1\),其餘點深度不變(當然也可以先按不刪除的做,然後給所有點深度減\(1\),二者本質相同)。

由於\(Spaly\)\(Splay\)一樣,是一棵平衡樹,因此中序遍歷是有序的,因此我們只要記錄每個點的父節點,就可以方便地求出子樹對應值域了。

插入新值

在平衡樹中插入一個新值實際上是有規律的。

我們可以把平衡樹從父節點轉移到子節點這一步看作一個類似於二分的過程,則顯然,插入一個數必然會經過它的前驅和後繼對應的點,且最後到達的點一定是前驅和後繼中深度較大的點(這個可以自己畫圖理解),那麼它的深度就是這個較大深度加\(1\)

要維護前驅和後繼,直接用\(set\)

就好了(當然如果你閒著沒事也可以去寫個\(Splay\)。。。)。

具體實現可能需要注意點細節。

樹狀陣列

整理一下我們要幹些什麼:單點賦值、區間加法、單點求值。

顯然一個樹狀陣列就可以了,其中單點賦值可以轉化為先單點求值,然後在原值的基礎上修改一個長度為\(1\)的區間。

程式碼

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,a[N+5],op[N+5],qv[N+5];struct node {int F,S[2];}O[N+5];set<int> P;set<int>::iterator it;
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define D isdigit(c=tc())
		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
		Tp I void writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('\n');}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
		#undef D 
}F;
class TreeArray
{
	private:
		int a[N+5];I void D(RI x,CI v) {W(x<=n) a[x]+=v,x+=x&-x;}
	public:
		I int Q(RI x,RI t=0) {W(x) t+=a[x],x-=x&-x;return t;}//單點求值
		I void U(CI l,CI r,CI v) {D(l,v),D(r+1,-v);}//區間修改
		I void T(CI x,CI v) {RI t=v-Q(x);D(x,t),D(x+1,-t);}//單點賦值
}S;
int main()
{
	RI Qt,i;for(F.read(Qt),i=1;i<=Qt;++i) F.read(op[i]),op[i]==1&&(F.read(qv[i]),a[++n]=qv[i]);//儲存詢問,因為要離散化
	#define Co(x,y,d) (O[O[x].F=y].S[d]=x)
	RI rt,x,u,v,du,dv;for(sort(a+1,a+n+1),i=1;i<=Qt;++i) switch(op[i])
	{
		#define Ans(d) S.T(x,d),F.writeln(d)
		case 1:x=lower_bound(a+1,a+n+1,qv[i])-a;if(P.empty()) {rt=x,Ans(1);goto End;}//原樹是空樹
			du=(it=P.lower_bound(x))==P.begin()?0:S.Q(u=*--it),//前驅(注意考慮不存在的情況)
			dv=(it=P.upper_bound(x))==P.end()?0:S.Q(v=*it),//後記
			du>dv?(Ans(du+1),Co(x,u,1)):(Ans(dv+1),Co(x,v,0));End:P.insert(x);break;//當前深度為較大深度加1
		case 2:F.writeln(S.Q(x=*P.begin())),O[x].F&&//如果當前點是根則不處理
			(S.T(x,1),S.U(O[x].F,n,1),Co(O[x].S[1],O[x].F,0),Co(rt,x,1),O[rt=x].F=0);break;//修改深度以及父子關係
		case 3:F.writeln(S.Q(x=*--P.end())),O[x].F&&//同上
			(S.T(x,1),S.U(1,O[x].F,1),Co(O[x].S[0],O[x].F,1),Co(rt,x,0),O[rt=x].F=0);break;
		case 4:F.writeln(S.Q(x=*P.begin())),P.erase(x),//從set中刪除當前點
			S.U(x+1,O[x].F?O[x].F-1:n,-1),O[(O[x].F?O[O[x].F].S[0]:rt)=O[x].S[1]].F=O[x].F;break;//注意分是否為根節點討論
		case 5:F.writeln(S.Q(x=*--P.end())),P.erase(x),//同上
			S.U(O[x].F?O[x].F+1:1,x-1,-1),O[(O[x].F?O[O[x].F].S[1]:rt)=O[x].S[0]].F=O[x].F;break;
	}return F.clear(),0;
}