1. 程式人生 > 實用技巧 >左偏樹 學習筆記

左偏樹 學習筆記

左偏樹

左偏樹到底是什麼呢??? 左偏樹實際上是可合併的堆。

他的節點不僅存了他的權值,還存了一個比較重要的資訊 \(dis\)

dis 的定義: 一個節點到他的子樹中的葉子節點的最近距離。

維護\(dis\)有什麼用呢?

當我們合併兩個堆時,可能會導致堆退化成一條鏈,這樣我們的詢問操作的複雜度就會變成 O(n)。

這顯然不是我們想看到的。

而左偏樹就是靠 \(dis\) 來維護左右兩顆子樹的平衡。

性質

  1. 節點的權值小於他兒子節點的取值。

  2. 左偏樹的任意節點的左兒子的距離都要比右兒子大(不然他怎麼叫左偏樹呢霧

  3. 左偏樹任意節點的距離等於右兒子的距離加一。

證明如下:

根據左偏性,可以得到他左兒子的距離要小於右兒子的距離,
而左偏樹中距離的定義是一個節點到離其最近的外節點的距離,故為Dis(RightSon)+1。

  1. 一個n個節點的左偏樹的距離最大為 log(n+1)-1(不需要掌握

證明如下:

若左偏樹的距離為一定值,則結點數最少的左偏樹是完全二叉樹。

結點最少的話,就是左右兒子距離一樣,這就是完全二叉樹了。

若一棵左偏樹的距離為k,則這棵左偏樹至少有 2^{k+1}-1個節點。

距離為k的完全二叉樹高度也是k,節點數就是 2^{k+1}-1個。

這樣就可以證明性質四了。

因為 n>=2^{k+1}-1,所以 k<=log(n+1)-1。

操作

一 合併操作

首先,當我們要合併堆時,讓要保持堆的性質,假設我們要維護小根堆,我們就要讓根節點權值較小的放上邊。

然後再讓另一個樹與他的右子樹合併。

那麼為什麼要合併右子樹呢???

因為左子樹的距離是大於右子樹的,合併左子樹顯然不優。

當我們合併完後,可能會出現這樣一種情況,左子樹的距離小於右子樹,這時我們直接交換一下就行了。

程式碼

int merage(int x,int y)//返回根節點
{
	if(x == 0 || y == 0) return x + y;//如果有一個為空,直接返回,不用合併
	if(tr[x].val > tr[y].val) swap(x,y);//要滿足堆的性質
	tr[x].rc = merage(tr[x].rc,y);//合併右子樹和另外一棵樹
	fa[tr[x].rc] = x;
	if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);//滿足性質二
	tr[x].dis = tr[tr[x].rc].dis + 1;//滿足性質三
	return x;
}

二 插入一個點

一個點就相當於一棵樹,直接合並就行了。

程式碼同上。

三 刪除根節點

我們要刪根節點,等於把根節點孤立出來,這時候我們只需要合併左右兩顆子樹就行了

程式碼

void del(int x)
{
	tr[x].val = -1;//標記x已經被刪除
	fa[tr[x].lc] = tr[x].lc; fa[tr[x].rc] = tr[x].rc;//先把左右兒子的父親都設為他自己,方便以後合併
	fa[x] =  merage(tr[x].lc,tr[x].rc);//保持原有的父子關係不變
}

例題

P3377 模板左偏樹

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e6+7;
int n,m,opt,x,y;
int fa[N],dis[N];
struct node{
	int val;
	int lc,rc;
	int fa,dis;
}tr[N];
int inline read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int find(int x){
    if(tr[x].fa == x) return x;
    else return tr[x].fa = find(tr[x].fa);
}
int merage(int x,int y){
	if(x == 0 || y == 0) return x+y;
	if(tr[x].val > tr[y].val || (tr[x].val == tr[y].val && x > y)){
		swap(x,y);
	}
	tr[x].rc = merage(tr[x].rc,y);
	tr[tr[x].rc].fa = x;
	if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);
	tr[x].dis = tr[tr[x].rc].dis + 1;
	return x;
}
void del(int x){
	tr[x].val = -1;
	tr[tr[x].lc].fa = tr[x].lc, tr[tr[x].rc].fa = tr[x].rc;
	tr[x].fa = merage(tr[x].lc,tr[x].rc);
}
int main(){
	n = read(), m = read();
	for(int i = 1; i <= n; i++){
		tr[i].fa = i;
        tr[i].val = read();
	}
	while(m--){
		opt = read();
		if(opt == 1){
			x = read(); y = read();
			int fx = find(x) , fy = find(y);
			if(tr[x].val == -1 || tr[y].val == -1) continue;
			if(fx == fy) continue;
			tr[fx].fa = tr[fy].fa = merage(fx,fy);
		}
		else{
			x = read();
			if(tr[x].val == -1) cout<<-1<<endl;
			else{
				int y = find(x);
				printf("%d\n",tr[y].val);
				del(y);
			}
		}
	}
	return 0;
}

P2713 羅馬遊戲

左偏樹的裸題,和模板差不多

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e6+10;
char opt;
int n,t,x,y,fa[N];
struct node{
	int lc,rc;
	int val,dis;
}tr[N];
inline int read()
{
	int s = 0, w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();}
	return s * w;
}
int find(int x)
{
	if(fa[x] == x) return x;
	else return fa[x] = find(fa[x]);
}
int merage(int x,int y)
{
	if(x == 0 || y == 0) return x + y;
	if(tr[x].val > tr[y].val) swap(x,y);
	tr[x].rc = merage(tr[x].rc,y);
	fa[tr[x].rc] = x;
	if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);
	tr[x].dis = tr[tr[x].rc].dis + 1;
	return x;
}
void del(int x)
{
	tr[x].val = -1;
	fa[tr[x].lc] = tr[x].lc; fa[tr[x].rc] = tr[x].rc;
	fa[x] =  merage(tr[x].lc,tr[x].rc);
}
int main()
{
	n = read();
	for(int i = 1; i <= n; i++)
	{
		fa[i] = i;
		tr[i].val = read();
	}
	t = read();
	while(t--)
	{
		cin>>opt;
		if(opt == 'M')
		{
			x = read(); y = read();
			int xx = find(x), yy = find(y);
			if(tr[x].val == -1 || tr[y].val == -1) continue;
			if(xx == yy) continue;
			fa[xx] = fa[yy] = merage(xx,yy);
		}
		else if(opt == 'K')
		{
			x = read();
			int xx = find(x);
			if(tr[x].val == -1)
			{
				printf("%d\n",0);
				continue;
			}
			printf("%d\n",tr[xx].val);
			del(xx);
		}
	}
	return 0;
}

P1456 Monkey King

暫時沒寫,先把坑占上。。。。

ENDING