日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

POJ - 2201 Cartesian Tree(笛卡尔树-单调栈/暴跳父亲)

發(fā)布時(shí)間:2024/4/11 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 POJ - 2201 Cartesian Tree(笛卡尔树-单调栈/暴跳父亲) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

題目鏈接:點(diǎn)擊查看

題目大意:給出n個(gè)節(jié)點(diǎn)的key和val,構(gòu)造出其笛卡爾樹的原型

笛卡爾樹的定義:

所謂笛卡爾樹,就是將給定的n個(gè)二元組(key,val)建成一棵樹。使得:

  • 如果只關(guān)注key,那么這是一棵二叉搜索樹
  • 如果只關(guān)注val,那么這是一個(gè)堆
  • 對(duì)于任意三個(gè)節(jié)點(diǎn)fa,ls,rs,滿足:
  • key[ls]<key[fa]<key[rs]
  • val[fa]>=max(val[ls],val[rs])
  • 題目分析:首先我們需要先知道怎么建立笛卡爾樹,我們可以按照key進(jìn)行排序,經(jīng)過排序之后,在最右邊的樹鏈上操作,對(duì)于每一個(gè)節(jié)點(diǎn)的val尋找一下該節(jié)點(diǎn)需要插入的位置,需要滿足的條件是當(dāng)前節(jié)點(diǎn)插入后的val值滿足條件比其上面的節(jié)點(diǎn)要大,比起下面的節(jié)點(diǎn)要小,找到合適的位置后,將原本的鏈斷開,將x插入,并將原本x之下的那條鏈連接到x的左子樹上,這樣我們就成功插入一個(gè)節(jié)點(diǎn)x了

    先說一下為什么要這樣插點(diǎn),因?yàn)槲覀冃枰獫M足二叉搜索樹的條件并且已經(jīng)提前按照key值非降排好序了,所以在插入節(jié)點(diǎn)x之前,樹中的所有key值都比節(jié)點(diǎn)x的key值要小,若我們只關(guān)注key的話,那么直接把點(diǎn)x插入到主根root節(jié)點(diǎn)向右遍歷最下面的那個(gè)節(jié)點(diǎn)的右兒子即可,也就是最右端樹鏈的末端,因?yàn)橥瑫r(shí)需要滿足對(duì)于val值建立小頂堆的條件,所以我們需要在最右端的樹鏈上尋找一個(gè)合適的位置插入節(jié)點(diǎn)x,當(dāng)找到合適的位置后,這個(gè)位置肯定是滿足了對(duì)于val值建堆的條件了,但卻不滿足對(duì)于key建二叉搜索樹的條件了,所以我們需要稍微操作一下,因?yàn)楫?dāng)前x的key值在整棵樹中是最大的,所以對(duì)于其父節(jié)點(diǎn)以及其祖先節(jié)點(diǎn),肯定在右鏈的末端是合適的位置,但對(duì)于節(jié)點(diǎn)x下面的樹鏈,也就是原本這個(gè)位置右鏈的下半部分來說,我們需要將這下半部分轉(zhuǎn)移到當(dāng)前節(jié)點(diǎn)x的左鏈上,這樣就可以同時(shí)滿足上述兩個(gè)條件了

    可能看起來比較難理解,可以自己動(dòng)手畫一下上面的這個(gè)過程,我也是稍微畫了畫圖之后就豁然開朗了

    現(xiàn)在說一下實(shí)現(xiàn)方法,對(duì)于key排序沒什么好說的,現(xiàn)在就是對(duì)于每個(gè)點(diǎn)x需要找到點(diǎn)x插入的位置,這個(gè)位置肯定是從右鏈最下面開始,找到小于等于點(diǎn)x的第一個(gè)節(jié)點(diǎn)作為點(diǎn)x的父節(jié)點(diǎn),我們?cè)撛趺纯焖僬业竭@個(gè)父節(jié)點(diǎn)呢

    網(wǎng)上的正解都是清一色的用單調(diào)棧來維護(hù),確實(shí),因?yàn)槲覀冎恍枰谟益溕蠈ふ液线m的位置,而且整棵樹對(duì)于val滿足堆的性質(zhì),所以從根部沿著右鏈到達(dá)葉子結(jié)點(diǎn),這一整條樹鏈實(shí)際上就是一個(gè)非嚴(yán)格遞減的序列,這樣一來我們就可以直接用單調(diào)棧來維護(hù)了,每次維護(hù)完之后棧頂就是我們需要尋找的節(jié)點(diǎn)編號(hào)了,注意,單調(diào)棧里維護(hù)的為每個(gè)節(jié)點(diǎn)的下標(biāo),也就是id,通過這個(gè)id就可以直接訪問其內(nèi)部成員了,用單調(diào)棧的話因?yàn)槊總€(gè)成員至多訪問一次,所以時(shí)間復(fù)雜度也就是O(n)建樹了

    不過zx學(xué)長想出了另一個(gè)方法,也就是根據(jù)建樹的整個(gè)過程得到啟發(fā)的,因?yàn)槊看挝覀冋业揭粋€(gè)放置點(diǎn)x的位置后,整個(gè)樹鏈的下半部分都會(huì)拐個(gè)彎,拐到節(jié)點(diǎn)x的左節(jié)點(diǎn)去了,所以此時(shí)最右邊的樹鏈相當(dāng)于將原本下半部分的節(jié)點(diǎn)刪除掉了,然后此時(shí)的點(diǎn)x一定是最右端樹鏈的最末端的節(jié)點(diǎn),此時(shí)我們每次可以沿著最末端的節(jié)點(diǎn)往上跳,跳完之后直接將下半部分的樹鏈拐彎,這樣理論上每個(gè)節(jié)點(diǎn)至多也是遍歷一次,時(shí)間復(fù)雜度是O(n)

    其實(shí)兩種方法大同小異,都離不開笛卡爾樹的兩個(gè)關(guān)鍵性質(zhì),理解了如何根據(jù)其性質(zhì)建樹之后就想怎么建就怎么建了。。

    代碼:

    單調(diào)棧:

    #include<iostream> #include<cstdlib> #include<string> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #include<cmath> #include<cctype> #include<stack> #include<queue> #include<list> #include<vector> #include<set> #include<map> #include<sstream> using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=5e4+100;struct Node {int l,r,fa,val,key,id;bool operator<(const Node& a)const{return key<a.key;} }tree[N],ans[N];stack<int>s;//維護(hù)id,維護(hù)val的小頂棧 void insert(int x) {while(s.size()&&tree[s.top()].val>tree[x].val)//單調(diào)棧維護(hù)非嚴(yán)格遞增序列s.pop();tree[x].l=tree[s.top()].r;//更新 節(jié)點(diǎn)x 的左兒子tree[tree[s.top()].r].fa=x;//更新 節(jié)點(diǎn)x 的左兒子 的父節(jié)點(diǎn)tree[x].fa=s.top();//更新 節(jié)點(diǎn)x 的父節(jié)點(diǎn)tree[s.top()].r=x;//更新 節(jié)點(diǎn)x 的父節(jié)點(diǎn) 的右兒子s.push(x); }void init() {tree[0].val=-inf;tree[0].l=tree[0].r=0;s.push(0); }int main() { // freopen("input.txt","r",stdin); // ios::sync_with_stdio(false);int n;while(scanf("%d",&n)!=EOF){init();for(int i=1;i<=n;i++){scanf("%d%d",&tree[i].key,&tree[i].val);tree[i].id=i;}sort(tree+1,tree+1+n);for(int i=1;i<=n;i++)insert(i);for(int i=1;i<=n;i++){ans[tree[i].id].fa=tree[tree[i].fa].id;ans[tree[i].id].l=tree[tree[i].l].id;ans[tree[i].id].r=tree[tree[i].r].id;}printf("YES\n");for(int i=1;i<=n;i++)printf("%d %d %d\n",ans[i].fa,ans[i].l,ans[i].r);}return 0; }

    暴跳父親:

    #include<iostream> #include<cstdlib> #include<string> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #include<cmath> #include<cctype> #include<stack> #include<queue> #include<list> #include<vector> #include<set> #include<map> #include<sstream> using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=5e4+100;struct Node {int l,r,fa,val,key,id;bool operator<(const Node& a)const{return key<a.key;} }tree[N],ans[N];void insert(int pos) {int cur=pos-1;while(tree[cur].val>tree[pos].val)//用while暴跳父親,直到父親節(jié)點(diǎn)的val小于等于當(dāng)前節(jié)點(diǎn)的valcur=tree[cur].fa;tree[tree[cur].r].fa=pos;//更新 節(jié)點(diǎn)x 的左兒子 的父節(jié)點(diǎn)tree[pos].l=tree[cur].r;//更新 節(jié)點(diǎn)x 的左兒子tree[cur].r=pos;//更新 節(jié)點(diǎn)x 的父節(jié)點(diǎn) 的右兒子tree[pos].fa=cur;//更新節(jié)點(diǎn)x 的父節(jié)點(diǎn) }void dfs(int x) {if(!x)return;ans[tree[x].id].fa=tree[tree[x].fa].id;ans[tree[x].id].l=tree[tree[x].l].id;ans[tree[x].id].r=tree[tree[x].r].id;dfs(tree[x].l);dfs(tree[x].r); }void init() {tree[0].val=-inf;tree[0].l=tree[0].r=0; }int main() { // freopen("input.txt","r",stdin); // ios::sync_with_stdio(false);int n;while(scanf("%d",&n)!=EOF){init();for(int i=1;i<=n;i++){scanf("%d%d",&tree[i].key,&tree[i].val);tree[i].id=i;}sort(tree+1,tree+1+n);for(int i=1;i<=n;i++)insert(i);dfs(tree[0].r);printf("YES\n");for(int i=1;i<=n;i++)printf("%d %d %d\n",ans[i].fa,ans[i].l,ans[i].r);}return 0; }

    ?

    總結(jié)

    以上是生活随笔為你收集整理的POJ - 2201 Cartesian Tree(笛卡尔树-单调栈/暴跳父亲)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。