【题解】LuoGu1040:加分二叉树

原题传送门
区间dp
根据前序遍历的特点,对于一段区间 [ l , r ] [l,r] [l,r],如果选择 r t ( l < = r t < = r ) rt(l<=rt<=r) rt(l<=rt<=r)作为根,那么这段区间的分数是 分 数 [ l , r t − 1 ] ∗ 分 数 [ r t + 1 , r ] + v a l r t 分数_{[l,rt-1]}*分数_{[rt+1,r]}+val_{rt} [l,rt1][rt+1,r]+valrt
所以可以令 d p i , j dp_{i,j} dpi,j表示 [ l , r ] [l,r] [l,r]的答案

  • d p i , i = v a l i dp_{i,i}=val_i dpi,i=vali
  • d p i , j = m a x ( d p i , k − 1 ∗ d p k + 1 , r + v a l k ) dp_{i,j}=max(dp_{i,k-1}*dp_{k+1,r}+val_k) dpi,j=max(dpi,k1dpk+1,r+valk)

同时为了输出前序遍历,在更新 d p dp dp数组的时候顺便更新 r t i , j rt_{i,j} rti,j表示这一段区间的根

Code:

#include 
#define maxn 110
using namespace std;
int dp[maxn][maxn], rt[maxn][maxn], n;

inline int read(){
	int s = 0, w = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
	for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
	return s * w;
}

void print(int l, int r){
	printf("%d ", rt[l][r]);
	if (l < rt[l][r]) print(l, rt[l][r] - 1);
	if (rt[l][r] < r) print(rt[l][r] + 1, r);
}

int main(){
	n = read();
	for (int i = 1; i <= n; ++i) dp[i][i] = read(), rt[i][i] = i;
	for (int l = 2; l <= n; ++l)
		for (int i = 1, j = i + l - 1; j <= n; ++i, ++j){
			if (dp[i + 1][j] + dp[i][i] > dp[i][j]) dp[i][j] = dp[i + 1][j] + dp[i][i], rt[i][j] = i;
			if (dp[i][j - 1] + dp[j][j] > dp[i][j]) dp[i][j] = dp[i][j - 1] + dp[j][j], rt[i][j] = j;
			for (int k = i + 1; k < j; ++k)
				if (dp[i][k - 1] * dp[k + 1][j] + dp[k][k] > dp[i][j])
					dp[i][j] = dp[i][k - 1] * dp[k + 1][j] + dp[k][k], rt[i][j] = k;
		}
	printf("%d\n", dp[1][n]);
	print(1, n);
	return 0;
}

你可能感兴趣的:(题解,LuoGu,DP,题解,LuoGu,DP)