洛谷 P1040 加分二叉树

题目传送门

其实看到正解是 区间DP 时还是很震惊的。

但是从题面来看,他说给出中序遍历再让你求出加分最大的情况下的先序遍历,还是可以从 二叉树的重构 中汲取些灵感的。


1.二叉树的重构

先不管什么情况下加分最大,假如现在就给你二叉树的中序遍历,让你输出它的先序遍历所有可能情况的数量(其实就是问你一个中序遍历所对应的二叉树有多少种,即二叉树的重构)。

这其实也是一道 区间DP

在中序遍历中,每个编号都可以作为根,如:

\(\space1\space2\space3\space4\space5\)

\(1\) 可以为根,那么它就 没有左子树,所有 \(1\) 右边的数都是它 右子树中的节点

\(3\) 可以为根,它的左子树就是它左边所有节点,它的右子树就是它所有右边的节点;

\(\dots\)

以此类推。

发现根节点的的子树也是一段区间,那么这个对应的区间,一定也有一个 对应的子树的根节点

初见递归端倪。

因此假设 \(dp(i,j)\) 为这段区间的 重构二叉树 的数量,那么枚举这段区间的根节点 \(k\),就可以得到递推式:

\[dp(i,j)=(\sum^{j-1}_{k=i+1}{dp(i,k-1)*dp(k+1,j)})+dp(i+1,j)+dp(i,j-1) \]

特别的:\(dp(i,i)=1\),即只有一个节点时,它的重构二叉树只有一个。

之所以把 \(dp(i+1,j)+dp(i,j-1)\) 单拿出来,是因为根为 \(i\)\(j\) 时,没有左子树或右子树。


2.终于入题:区间DP

\(dp[i][j]\) 表示该区间所能获得的最大加分,那么很容易写出转移方程:

\[dp[i][j]=max^{j}_{k=i}{dp[i][k-1]*dp[k+1][j]+a[k]} \]

特殊处理 \(k=i\)\(k=j\) 时,左子树或右子树加分为 \(1\) 的情况。

至于输出先序遍历,用一个数组 \(rt\) 记录转移就好了。

#include <bits/stdc++.h>

#define mkpr make_pair

namespace FIO {
	template<typename T>
	T read() {
		T x = 0, f = 1; char c = getchar();
		while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
		while (c >= '0' && c <= '9') {x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}
		return x * f;
	}
	template<typename T>
	void print(T x) {
		if (x < 0) x = -x, putchar('-');
		if (x > 9) print(x / 10);
		putchar(x % 10 + 48);
	}
}

using namespace FIO;
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef __int128 INT;
typedef pair<int, int> pii;

const int maxn = 3e3 + 7;
const int inf  = 0x3f3f3f3f;


int n, a[maxn];

ll dp[maxn][maxn];
int rt[maxn][maxn];

void print_path(int l, int r) {
	if (!rt[l][r]) return ;
	print(rt[l][r]), putchar(' ');
	print_path(l, rt[l][r] - 1);
	print_path(rt[l][r] + 1, r);
}
int main() {
	n = read<int>();
	for (int i = 1; i <= n; ++i)
	    dp[i][i] = a[i] = read<int>(), rt[i][i] = i;
	    
	for (int l = 2; l <= n; ++l) {
		for (int i = 1; i <= n; ++i) {
			int j = i + l - 1;
			for (int k = i; k <= j; ++k) {
				if (k == i) {
					if (dp[i][j] < 1 * dp[k + 1][j] + a[k]) {
						dp[i][j] = 1 * dp[k + 1][j] + a[k];
						rt[i][j] = k;
					}
				} else if (k == j) {
					if (dp[i][j] < 1 * dp[i][k - 1] + a[k]) {
						dp[i][j] = 1 * dp[i][k - 1] + a[k];
						rt[i][j] = k;
					}
				} else {
					if (dp[i][j] < dp[i][k - 1] * dp[k + 1][j] + a[k]) {
						dp[i][j] = dp[i][k - 1] * dp[k + 1][j] + a[k];
						rt[i][j] = k;
					}
				}
			}
		}
	}
	
	print(dp[1][n]), putchar('\n');
	print_path(1, n), putchar('\n');
	return 0;
} 
posted @ 2024-10-25 15:19  syzyc  阅读(76)  评论(0)    收藏  举报