题目描述
设一个 n 个节点的二叉树 tree 的中序遍历为(1,2,3,…,n),其中数字 1,2,3,…,n 为节点编号。每个节点都有一个分数(均为正整数),记第 i 个节点的分数为 di,tree 及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree 本身)的加分计算方法如下:
subtree 的左子树的加分 × subtree 的右子树的加分 + subtree 的根的分数。
若某个子树为空,规定其加分为 1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为 (1,2,3,…,n) 且加分最高的二叉树 tree。要求输出
-
tree 的最高加分。
-
tree 的前序遍历。
思路:dp
dp表
使用二维的dp表,存放树的起点和终点。
状态
-
初状态:
-
dp 数组的初状态,即为题意中的空树分数为 1。
考虑空树是不可能成立的 dp[i][j],因此只需要将 i>j 的部分置为 1 即可。
另外,由于循环计算时只需要考虑下标 $ \pm 1$ 的情形(见状态转移方程),因此,dp 数组的初状态可为下列两种的任意一种:
- str 数组的初状态,即为树中只有一个节点的情况。
1
| str[i][i] = to_string(i);
|
-
状态转移方程:
需要注意的是,i 和 j 并不能确定一个树,因此需要在 i∼j 中循环枚举根节点 k。
str 数组可能存在值为空的情况,因此需要判断字符串的长度是否为 0,如果不为 0 则说明字符串包含数字,需要在头尾加空格。
1 2 3
| maxset(dp[i][j], dp[i][k - 1] * dp[k + 1][j] + score[k]);
str[i][j] = str[k][k] + (str[i][k - 1].size() ? " " : "") + str[i][k - 1] + (str[k + 1][j].size() ? " " : "") + str[k + 1][j];
|
-
末状态:dp[1][n] 为分数,str[1][n] 为前序遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <bits/stdc++.h>
using namespace std; using ll = long long;
inline void minset(int &t, int other) { t = min(t, other); }
inline void maxset(int &t, int other) { t = max(t, other); }
const int inf = 0x7f7f7f7f;
const int N = 31;
unsigned int dp[N][N]; string str[N][N]; int score[N];
int main() { int n; cin >> n; for(int i = 1; i <= n; i++) { cin >> score[i]; } for(int i = 1; i <= n; i++) { dp[i][i - 1] = dp[i + 1][i] = 1; str[i][i] = to_string(i); } for(int i = n; i >= 1; i--) { for(int j = i; j <= n; j++) { for(int k = i; k <= j; k++) { const unsigned int curr_score = !(k == i && k == j) * dp[i][k - 1] * dp[k + 1][j] + score[k]; if(curr_score > dp[i][j]) { dp[i][j] = curr_score; str[i][j] = str[k][k] + (str[i][k - 1].size() ? " " : "") + str[i][k - 1] + (str[k + 1][j].size() ? " " : "") + str[k + 1][j]; } } } } cout << dp[1][n] << '\n' << str[1][n] << endl; return 0; }
|
补充
三维数组唯一确定树
如果用三维数组 dp[i][j][k] 来确定树(i∼j,根为 k),并不能简化状态转移。因为:
1
| maxset(dp[i][j][k], dp[i][???][k - 1] * dp[k + 1][???][j] + dp[j][j][j]);
|
仍然需要遍历下标确定子树的根。
简化str的状态转移
简化方式
可以在有数字的字符串后面添加空格来避免判断。
简化后状态
-
初状态:
1
| str[i][i] = to_string(i) + " ";
|
-
状态转移方程:
1
| str[i][j] = str[k][k] + str[i][k - 1] + str[k + 1][j];
|
- 需要注意的是,这种做法会在字符串末尾产生一个空格。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <bits/stdc++.h>
using namespace std; using ll = long long;
inline void minset(int &t, int other) { t = min(t, other); }
inline void maxset(int &t, int other) { t = max(t, other); }
const int inf = 0x7f7f7f7f;
const int N = 31;
unsigned int dp[N][N]; string str[N][N]; int score[N];
int main() { int n; cin >> n; for(int i = 1; i <= n; i++) { cin >> score[i]; } for(int i = 1; i <= n; i++) { dp[i][i - 1] = dp[i + 1][i] = 1; str[i][i] = to_string(i) + " "; } for(int i = n; i >= 1; i--) { for(int j = i; j <= n; j++) { for(int k = i; k <= j; k++) { const unsigned int curr_score = !(k == i && k == j) * dp[i][k - 1] * dp[k + 1][j] + score[k]; if(curr_score > dp[i][j]) { dp[i][j] = curr_score; str[i][j] = str[k][k] + str[i][k - 1] + str[k + 1][j]; } } } } cout << dp[1][n] << '\n' << str[1][n] << endl; return 0; }
|
为什么用unsigned int?
普通int的最大值为231−1,即2,147,483,647。而本题的ans≤4,000,000,000,因此有可能爆int。(虽然用int也能过)