** 國家集訓隊 何森 **
【問題描述】
什麼是樹形揹包?簡單的說就是在一棵有n個節點的樹上,每個節點都相當於一件物品,每件物品都有乙個**和乙個得分對於物品a,設其父節點為father[a],那麼你必須購買了father[a]後才能購買a。現給你m元現金,要求求乙個購買方案,使得物品的總得分最大。
我們見過很多此類題目:重修道路、技能樹、選課……
【分析】
我們很容易想到乙個樹形動態規劃:
cost[a]表示a節點所代表的物品的**
score[a]表示a節點所代表的物品的得分
狀態f[a][b]表示以節點a為根的子樹,總共花費不超過b元的最多得分。
狀態轉移方程:
f[a][b]=max+score[a];
其中ci為a的子節點;∑bi=b-cost[a];
這樣列舉的效率顯然不高。
於是我們想到樹形dp的常用優化方法:利用左兒子右兄弟表示法
這樣f[a][b]表示以節點a為根的子樹且及其兄**樹總共花費不超過b元的最多得分。
f[a][b]=max(max,f[right[a]][b]);
其中left[a],right[a]分別表示a的左、右子樹。
這樣我們得到了乙個o(m2n)的動態規劃。
然而,我們探索的腳步還未停下。
回想經典的01揹包問題。
f[a][b]表示從第a個物品到第n個物品總共花費不超過b元所能得到的最大得分。顯然我們要做的就是列舉乙個物品買還是不買。
f[a][b]=max(f[a+1][b-cost[a]]+money[a],f[a+1][b]);
複雜度是o(m*n)的。
為什麼這個問題可以做到o(m*n)呢,樹形揹包可以優化到這個複雜度嗎?
我們來**樹形揹包的複雜度是怎麼來的。
我們從最原始的多叉樹說起:
在狀態轉移的時候,我們需要列舉給每棵子樹分配的現錢數,這導致了列舉量巨大,演算法效率不高。
根據這一點我們把多叉樹轉化成了二叉樹,目的就是要減少列舉量,顯然我們做到了,在二叉樹上我們只需要列舉給左子樹分配多少。
關於樹形揹包先研究到這裡,我們先看看經典的揹包是怎麼回事:
當我們在考慮乙個物品a的時候,只用列舉是否要購買它,而且注意到不論我們是否要購買,剩下的錢都由後面所有的物品來分。
回到樹上來。
我們發現,在乙個分叉點上我們的現錢是需要分流的,即給每一棵子樹一部分。
而且在子樹中不會相互轉移了。
本質的差異就在這裡。
這時,我們就有了乙個美妙的設想:將左子樹和右子樹連線起來,使得現錢在考慮完左子樹後能再供右子樹分配。這樣,我們所想的實際上就是乙個線形關係。
關於樹的線形關係,我們立刻想到樹的遍歷序。
並且由於需要先處理父節點,從直覺上我們先選擇前序遍歷序。
這樣,我們得到了乙個線形關係。
注意樹的前序遍歷序並不能完全表示一棵樹的結構。
我們需要記下每個節點的子孫節點總數childnum。
這時,我們看到了很美好的景像:
如我們所願,樹的結構成了 根—>左子樹—>右子樹 這樣的線形結構。
假設我們將前序遍歷序儲存在list裡面。
我們定義動態規劃的狀態:
f[a][b]表示從a到n總共花費b元所得的最大得分。
則我們有轉移方程:
f[a][b]=max;
即:列舉買不買a節點所代表的物品。如果買的話則轉到線形表的下乙個結點(a節點的後繼),否則a節點所在的子樹就不用討論了,直接將錢轉移到以a為根的子樹的後繼上去。
這樣,我們就不必給子樹分錢了,而是所有的子樹共同分享剩下的錢。
根據前面的思考,我們先依次求出每棵樹的先根遍歷序,並儲存在同乙個序列list中。
為了利用上面的結論,我們還要求出以節點i為根的子樹的節點總數count[i]。
現在我們來設計動態規劃演算法:
定義:
cost[i]表示第i個物品的**
weight[i]表示第i個物品的權值
f[i][j]表示從第i個物品到第n個物品,最多花費j元,能得到的最大權值和。
狀態轉移:
對於乙個節點,我們考慮是否購買它:
購買:那麼我們繼續考慮它後面的節點
不購買:那麼我們跳過它的子孫節點
方程如下:
f[i][j]=max
這個演算法依然是o(nm)的,很完美!
先序遍歷用於優化樹形揹包問題
問題描述 什麼是樹形揹包?簡單的說就是在一棵有 個節點的樹上,每個節點都相當於一件物品,每件物品都有乙個 和乙個得分對於物品a,設其父節點為father a 那麼你必須購買了father a 後才能購買a。現給你m元現金,要求求乙個購買方案,使得物品的總得分最大。我們見過很多此類題目 重修道路 技能...
求先序遍歷
如下 include struct treenode treenode binarytreefromorderings char inorder,char aftorder,int length treenode node new treenode 新建乙個樹節點 node elem aftorde...
根據後序和中序遍歷輸出先序遍歷 (25 分)
本題要求根據給定的一棵二叉樹的後序遍歷和中序遍歷結果,輸出該樹的先序遍歷結果。第一行給出正整數n 30 是樹中結點的個數。隨後兩行,每行給出n個整數,分別對應後序遍歷和中序遍歷結果,數字間以空格分隔。題目保證輸入正確對應一棵二叉樹。在一行中輸出preorder 以及該樹的先序遍歷結果。數字間有1個空...