【OI賽第五場】T1樹

題意
一顆二叉樹,知道中序遍歷是1~n,告訴你層次遍歷,求字典序最小的先序遍歷。
樣例輸入
5
4 2 5 1 3
樣例輸出
4 2 1 3 5
數據範圍:n<=3*10^5

題解
題目的關鍵是把這顆樹建出來,怎麼建呢?就是找根,根就是一段區間在層次遍歷中出現最早的,然後遞歸左右子樹,現在的關鍵是求一段區間的最早出現的值。用一個B數組記錄1~n中每個數出現的時間,用ST表,或線段樹就可以A掉這題。複雜度O(nlogn);但這不是本題的重點。重點是用笛卡爾樹O(n)的求。下面介紹一下笛卡爾樹。

笛卡爾樹

笛卡爾樹又稱笛卡兒樹,在數據結構中屬於二叉樹的一種。

笛卡爾樹結構由Vuillmin在解決範圍搜索的幾何數據結構問題時提出的,從數列中構造一棵笛卡爾樹可以線性時間完成,需要採用基於棧的算法來找到在該數列中的所有最近小數。由此可知,笛卡爾樹是一種特定的二叉樹數據結構,可由數列構造,在範圍最值查詢、範圍top k查詢(range top k queries)等問題上有廣泛應用。它具有堆的有序性,中序遍歷可以輸出原數列。

笛卡爾樹是一棵二叉樹,樹的每個節點有兩個值,一個爲key,一個爲value。光看key的話,笛卡爾樹是一棵二叉搜索樹,每個節點的左子樹的key都比它小,右子樹都比它大;光看value的話,笛卡爾樹有點類似堆,根節點的value是最小(或者最大)的,每個節點的value都比它的子樹要大。

構造笛卡爾樹的過程:

使用數據結構棧,棧中保存的始終是右鏈,即根結點、根結點的右兒子、根結點的右兒子的右兒子……組成的鏈
並且棧中從棧頂到棧底key依次減小


如果按照從後到前的順序判斷一個元素是否大於A[i],則每次插入的時間複雜度爲O(k+1)
k爲本次插入中移除的右鏈元素個數。因爲每個元素最多進出右鏈各一次,所以整個過程的時間複雜度爲O(N)。

從前往後遍歷A[i],
1.對於每一個A[i],從棧中找出(從棧頂往棧底遍歷,或者從數組後往前遍歷)第一個小於等於A[i]的元素
2.如果找到,i.parent爲sta[k],同時sta[k].r=i,即i爲sta[k]的右子樹,
3.如果棧中存在比A[i]大的元素 這些元素肯定是出棧了,這個問題最後的代碼統一表示。
同時,sta[k+1].parent=i; i.l=sta[k+1] 即sta[K+1]爲i的左子樹
4.最後i入棧,比i大的A[i]都自動出棧了。


例子如下。
0 1 2 3 4 5 6 7 8  9      .....key
3 2 4 5 6 8 1 9 10 7      .....A,value

stack
0 1 2 3 4 5 6 7 8  ...num
0
1 2 3 4 5
6 7 8
6 9
最後sta[0].parent=-1;  爲根節點 即 6 爲根節點。

這裏給出的是索引從0開始的[0,n-1]
如果題目給出的是[1,n],可以減一回到[0,n-1]上。

這就是笛卡爾樹的概念及建樹過程。此題就可以把中序遍歷1~n當成第一維權值出現的時間(滿足中序遍歷是1~n),把每個點的出現的時間(兩個時間不要混)當做第二位權值建小根堆(因爲在層次遍歷出現時間越早越可能是根)。就可以O(n)建樹然後遍歷一遍求先序遍歷了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=300010;
int s[maxn],a[maxn],b[maxn],x,n;
struct tree{
	int fa,l,r,val;
}t[maxn];
inline int build(){
	int top=-1,k;
	for(int i=1;i<=n;i++){
		k=top;
		while(k>=0&&b[s[k]]>b[i])//棧中比當前元素大的都出棧 
		k--;
		if(k!=-1){//find it,棧中元素沒有完全出棧,當前元素爲棧頂元素的右孩子
			t[i].fa=s[k];
			t[s[k]].r=i;
		}
		if(k<top){//出棧的元素爲當前元素的左孩子 
			t[s[k+1]].fa=i;
			t[i].l=s[k+1];
		}
		s[++k]=i;
		top=k;
	}
	t[s[0]].fa=-1;//遍歷完成後的棧頂元素就是根
	return s[0];
}
inline void out(int x){
	if(x==-1) return ;
	printf("%d ",x);
	out(t[x].l);
	out(t[x].r);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		b[a[i]]=i;
	}
	for(int i=1;i<=n;i++){
		t[i].val=b[i];
	}
	for(int i=1;i<=n;i++)
	t[i].l=t[i].r=-1;
	x=build();
	out(x);

	return 0;
}

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