题目
题目链接: https://ac.nowcoder.com/acm/contest/19859/Q
题意:
有n个节点的二叉树tree的中序遍历为(l,2,3,…,n)。一棵子树的加分(权值) = 他的 左子树的加分(权值)*右子树的加分(权值),叶子的加分(分数)就是叶节点本身的分数
要求输出:
1.tree的最高加分。
2.tree的前序遍历。
输入
5
5 7 1 2 10
输出
145
3 1 2 4 5
#解析 :
*一,由于是中序遍历,所以我们可以考虑树形区间DP。求法类型与石子合拼(区间DP). 这里我利用了,子树的两种状态,1只有左子树或右子树(分割点在两端),2.左右子树都(分割点在中间),刚好对上了所有分割的点的位置(说明分析的状态一个不漏). 这里的状态对应的DP , 1. dp[i][j] = dp[i][k-1]dp[]k+1[j]+dp[k][k],2. dp[i][k]+dp[k+1][j] 或 dp[i][k-1]+dp[k][j]。
二,分析小状态,长度len为1,2,3的状态的解释。len=1, dp[][]=本身的值;len=2,状态2,只有一个子树;len=3时,1,2都有,一个for循环枚举分割点k即可。以此类推。
三,记录过程,即记录分割点的位置,我们不确定分割点的位置,所以我们需要记录所有的分割点,用vis[i][j]=t,表示区间[i,j]的分割点为t。当我们求出dp[1][n]的时候,分割的路线已经很明显了,递归打印分割点即可
#solution1, 常规DP
#solution2,记忆化DFS
AC代码 - - -
#solution1
import java.util.*;
import java.io.*;
public class Main {
static int n,a[],vis[][],p=1;
static boolean flag = true;
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static int ini() throws IOException{
int x=0,c=in.read();while(c<48||c>57){if(c==-1) return -1; c=in.read();}
while(c>=48&&c<=57){x=(x<<3)+(x<<1)+(c^48);c=in.read();}return x;
}
static void dfs(int l,int r) {
if(l>r||l<1||r>n) return ;
int pos = vis[l][r];
if(p!=n) {
System.out.printf("%d ",pos);
}else {
System.out.printf("%d\n",pos);
}
p++;
dfs(l,pos-1);
dfs(pos+1,r);
}
public static void main(String[] args) throws IOException{
n = ini();
a = new int[n+2];
vis = new int[n+2][n+2];
int dp[][]=new int[n+2][n+2];
for(int i = 1; i <= n; ++i) {
a[i] = ini();
dp[i][i] = a[i];
vis[i][i] = i;
}
for(int l = 2; l <= n; ++l) {
for(int i = 1; i <= n-l+1; ++i) {
int j = i+l-1;
int t = 0;
for(int k = i; k <= j; ++k) {
if(k==i) {
t = dp[i][k]+dp[k+1][j];dp[i][k-1]+dp[k][j];
}else if(k==j){
t = dp[i][k-1]+dp[k][j];
}else {
t = dp[i][k-1]*dp[k+1][j]+dp[k][k];
}
if(t>dp[i][j]) {
dp[i][j] = t;
vis[i][j] = k;
}
}
}
}
System.out.printf("%d\n",dp[1][n]);
dfs(1,n);
}
}