加分二叉樹


一道入門的區間dp,當然,根據寫法不同你還可以把它歸類為樹形dp或者記憶化搜索,其實都無所謂啦。
作為一道入門題,我們完全可以“顯然”地做出來,但是在這里還是想和大家回顧下動態規划以及區間動規。

Q:dp特點是什么?
A:dp把原問題視作若干個重疊的子問題的逐層遞進,每個子問題的求解過程都會構成一個“階段”,在完成一個階段后,才會執行下一個階段。
Q:dp要滿足無后效性,什么叫無后效性?
A:已經求解的子問題不受后續階段的影響。

有人覺得dp很抽象,那是因為沒有一步一步來想,直接聽別人的結論,我們在這里以這道題為例,一步一步來推導。

首先,我們要做的就是設計狀態,其實就是設計dp數組的含義,它要滿足無后效性。
關注這個 左子樹*右子樹+根 我只要知道左子樹分數和右子樹分數和根的分數(已給出),不就可以了嗎?管他子樹長什么樣!
所以,我們fff數組存的就是最大分數,怎么存呢?
我們發現:子樹是一個或多個節點的集合。
那么我們可不可以開一個f[i][j]f[i][j]f[i][j]來表示節點i到節點j成樹的最大加分呢?可以先保留這個想法(畢竟暫時也想不到更好的了)。

如果這樣話,我們就來設計狀態轉移方程。
按照剛剛的設計來說的話,我們的答案就是f[1][n]f[1][n]f[1][n]了,那么我們可以從小的子樹開始,也就是len,區間長度。有了區間長度我們就要枚舉區間起點,i為區間起點,然后就可以算出區間終點j。
通過加分二叉樹的式子我們可以知道,二叉樹的分取決於誰是根,於是我們就在區間內枚舉根k。
特別的,f[i][i]=a[i]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])f[i][j]=MAX(f[i][k1]f[k+1][j]+f[k][k]) 於是乎,我們就自己設計出了一個dp過程,因為是順着來的,所以很少有不成立的。

至於輸出前序遍歷,我們再設計一個狀態root[i][j]root[i][j]root[i][j]來表示節點i到節點j成樹的最大加分所選的根節點。
所以我們按照$根->左->右$的順序遞歸輸出即可。

代碼

#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int MAXN = 50; typedef long long ll; ll n; ll f[MAXN][MAXN], root[MAXN][MAXN]; void print(ll l, ll r) { if (l > r)return; printf("%lld ", root[l][r]); if (l == r)return; print(l, root[l][r] - 1); print(root[l][r]+1,r); } int main() { scanf("%lld", &n); for (int i = 1; i <= n; i++)scanf("%lld", &f[i][i]),f[i][i-1]=1, root[i][i] = i; for (int len = 1; len < n; ++len) { for (int i = 1; i + len <= n; ++i) { int j = i + len; f[i][j] = f[i + 1][j] + f[i][i];//默認它的左子樹為空,如果有的話,這肯定不是最優解 root[i][j] = i;//默認從起點選根 for (int k = i + 1; k < j; ++k) { if (f[i][j] < f[i][k - 1] * f[k + 1][j] + f[k][k]) { f[i][j] = f[i][k - 1] * f[k + 1][j] + f[k][k]; root[i][j] = k; } } } } cout << f[1][n] << endl; print(1, n); return 0; }

轉載於洛谷大佬冒泡ioa


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM