洛谷P1040 加分二叉樹(樹形dp)
加分二叉樹
時間限制: 1 Sec 內存限制: 125 MB提交: 11 解決: 7
題目描述
設一個n個節點的二叉樹tree的中序遍歷為(l,2,3,...,n),其中數字1,2,3,...,n為節點編號。每個節點都有一個分數(均為正整數),記第j個節點的分數為di,tree及它的每個子樹都有一個加分,任一棵子樹subtree(也包含tree本身)的加分計算方法如下:
subtree的左子樹的加分×subtree的右子樹的加分+subtree的根的分數
若某個子樹為主,規定其加分為1,葉子的加分就是葉節點本身的分數。不考慮它的空子樹。
試求一棵符合中序遍歷為(1,2,3,...,n)且加分最高的二叉樹tree。要求輸出:(2)tree的前序遍歷
輸入
第1行:一個整數n(n<30),為節點個數。第2行:n個用空格隔開的整數,為每個節點的分數(分數<100)。
輸出
第1行:一個整數,為最高加分(結果不會超過4,000,000,000)。第2行:n個用空格隔開的整數,為該樹的前序遍歷。
樣例輸入
5
5 7 1 2 10
樣例輸出
145
3 1 2 4 5
提示
來源
提高組-2003年NOIP
題目類型:
樹形dp
思路:
首先,我們要做的就是設計狀態,其實就是設計dp數組的含義,它要滿足無後效性。關註這個 左子樹*右子樹+根 我只要知道左子樹分數和右子樹分數和根的分數(已給出),不就可以了嗎?管他子樹長什麽樣!
所以,我們f數組存的就是最大分數,怎麽存呢?
我們發現:子樹是一個或多個節點的集合。
那麽我們可不可以開一個f[i][j],f[i][j]來表示節點i到節點j成樹的最大加分呢?可以先保留這個想法(畢竟暫時也想不到更好的了)。
如果這樣話,我們就來設計狀態轉移方程。按照剛剛的設計來說的話,我們的答案就是f[1][n]了,那麽我們可以從小的子樹開始,也就是len,區間長度。有了區間長度我們就要枚舉區間起點,i為區間起點,然後就可以算出區間終點j。通過加分二叉樹的式子我們可以知道,二叉樹的分取決於誰是根,於是我們就在區間內枚舉根k。
特別的,f[i][i]=a[i]f[i][i]=a[i],其中a[i]為第i個節點的分數。
因為是要求最大值,所以我們就可以設計出f[i][j]=MAX(f[i][k-1]*f[k+1][j]+f[k][k])f[i][j]=MAX(f[i][k−1]*f[k+1][j]+f[k][k])於是乎,我們就自己設計出了一個dp過程,因為是順著來的,所以很少有不成立的。
至於輸出前序遍歷,我們再設計一個狀態root[i][j]來表示節點i到節點j成樹的最大加分所選的根節點。所以我們按照$根->左->右$的順序遞歸輸出即可。
#include<bits/stdc++.h> using namespace std; const int MAXN = 50; typedef long long ll; ll n; //f[i][j]表示i到j子樹的最大分數 ll f[MAXN][MAXN],root[MAXN][MAXN]; void print(ll l, ll r) { if (l > r)return; cout<<root[l][r]<<" ";//打印根 if (l == r)return; print(l, root[l][r] - 1);//打印左子樹 print(root[l][r]+1,r);//打印右子樹 } int main() { cin>>n; memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) { cin>>f[i][i]; root[i][i]=i; } for(int len=1;len<n;len++)//以子樹的長度遍歷,1個節點的子樹,2個節點的子樹。。。 { for(int i=1;i+len<=n;i++) { int j=i+len; //根從i開始遍歷,先假設i到j的子樹,i為根 //不考慮它的空子樹,所以左子樹為空時,左子樹為1,而不是0 //f[i][j]=f[i+1][j]+f[i][i];//此時i到j子樹的分數 //root[i][j]=i; //cout<<i<<"到"<<j<<" 最大為:"<<f[i][j]<<" 以"<<i<<"為根"<<endl; for(int k=i;k<j;k++)//k為根,遍歷每一個根,找到能使子樹分數最大的根 { ll zhi = f[i][k-1]*f[k+1][j]+f[k][k]; //不考慮它的空子樹,所以左子樹為空時,左子樹為1,而不是0 //以右子樹為空時,右子樹為1,而不是0 if(f[i][k-1]==0) { zhi=f[k+1][j]+f[k][k]; } if(f[k+1][j]==0) { zhi=f[i][k-1]+f[k][k]; } if(f[i][j]<zhi) { f[i][j]=zhi; root[i][j]=k; //cout<<i<<"到"<<j<<" 最大為:"<<zhi<<" 以"<<k<<"為根"<<endl; } } } } cout<<f[1][n]<<endl; print(1, n); return 0; }
洛谷P1040 加分二叉樹(樹形dp)