1. 程式人生 > >洛谷P1040 加分二叉樹(樹形dp)

洛谷P1040 加分二叉樹(樹形dp)

class bits ... 來源 正整數 提高 ron urn int

加分二叉樹

時間限制: 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。要求輸出:
(1)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)