P1864 [NOI2009]二叉查找树

题目描述

已知一棵特殊的二叉查找树。根据定义,该二叉查找树中每个结点的数据值都比它左儿子结点的数据值大,而比它右儿子结点的数据值小。

另一方面,这棵查找树中每个结点都有一个权值,每个结点的权值都比它的儿子结点的权值要小。

已知树中所有结点的数据值各不相同;所有结点的权值也各不相同。这时可得出这样一个有趣的结论:如果能够确定树中每个结点的数据值和权值,那么树的形态便可以唯一确定。因为这样的一棵树可以看成是按照权值从小到大顺序插入结点所得到的、按照数据值排序的二叉查找树。

一个结点在树中的深度定义为它到树根的距离加 1。因此树的根结点的深度为 1。

每个结点除了数据值和权值以外,还有一个访问频度。我们定义一个结点在树中的访问代价为它的访问频度乘以它在树中的深度。整棵树的访问代价定义为所有结点在树中的访问代价之和。

现在给定每个结点的数据值、权值和访问频度,你可以根据需要修改某些结点的权值,但每次修改你会付出 K 的额外修改代价。你可以把结点的权值改为任何实数,但是修改后所有结点的权值必须仍保持互不相同。现在你要解决的问题是,整棵树的访问代价与额外修改代价的和最小是多少?

输入格式

输入文件中的第一行为两个正整数 N,K。其中 N 表示结点的个数,K 表示每次修改所需的额外修改代价。

接下来的一行为 N 个非负整数,表示每个结点的数据值。

再接下来的一行为 N 个非负整数,表示每个结点的权值。

再接下来的一行为 N 个非负整数,表示每个结点的访问频度。

其中:所有的数据值、权值、访问频度均不超过 \(4 \times 10^5\)

输出格式

输出文件中仅一行为一个数,即你所能得到的整棵树的访问代价与额外修改代价之和的最小值。

输入输出样例

输入 #1复制

4 10
1 2 3 4
1 2 3 4
1 2 3 4

输出 #1复制

29
样例解释没了。。
n <= 70

区间dp
\(f[l][r][l]\) 表示在区间\([l,r]\) 之间所有的权值都$ >= k$
的最优解。
转移时,枚举一个t当做根,若t的权值 >= k 那么
\(dp[i][j][k] = min(dp[i][j][k] , dp[i][t-1][s[t].b] + dp[t+1][j][s[t].b] + sum[j] - sum[i-1])\)
还有一个通用的转移,就是将t 的权值变成k
\(dp[i][j][k] = min(dp[i][j][k] , dp[i][t-1][k] + dp[t+1][j][k] + K + sum[j] - sum[i-1])\)
a 是数据值, b是权值, c访问频率
sum 是c的前缀和。
最后注意枚举的顺序,由于要用到两边的([i,t-1] 和 [t+1,j])
那个i要倒序枚举,

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<bitset>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
using namespace std;
const int N = 72;
inline int read()
{
    register int x = 0 , f = 0; register char c = getchar();
    while(c < '0' || c > '9') f |= c == '-' , c = getchar();
    while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0' , c = getchar();
    return f ? -x : x;
}
int n , K , tot;
LL dp[N][N][N] , sum[N];
struct node{ int a , b , c; }s[N];
inline bool cmp_a(const node &A , const node &B) { return A.a < B.a; }
inline bool cmp_b(const node &A , const node &B) { return A.b < B.b; }
int main()
{
	n = read(); K = read();
	for(int i = 1 ; i <= n ; ++i) s[i].a = read();
	for(int i = 1 ; i <= n ; ++i) s[i].b = read();
	for(int i = 1 ; i <= n ; ++i) s[i].c = read();
	sort(s + 1 , s + 1 + n , cmp_b);
	for(int i = 1 ; i <= n ; ++i) s[i].b = i; // ....
	sort(s + 1 , s + 1 + n , cmp_a);
	memset(dp , 0x3f , sizeof dp);
	for(int i = 1 ; i <= n+1 ; ++i) for(int j = 1 ; j <= n ; ++j) dp[i][i-1][j] = 0;
	for(int i = 1 ; i <= n ; ++i) sum[i] = sum[i-1] + s[i].c;
	for(int i = n ; i >= 1 ; --i) // 注意枚举顺序,需要用到后面的区间 
		for(int j = i ; j <= n ; ++j)
			for(int k = 1 ; k <= n ; ++k)
				for(int t = i ; t <= j ; ++t)
				{
					if(s[t].b >= k) 
						dp[i][j][k] = min(dp[i][j][k] , dp[i][t-1][s[t].b] + dp[t+1][j][s[t].b] + sum[j] - sum[i-1]);
					dp[i][j][k] = min(dp[i][j][k] , dp[i][t-1][k] + dp[t+1][j][k] + K + sum[j] - sum[i-1]); // 不只是s[t].b < k  
				}
					
	cout << dp[1][n][1] << '\n';
	return 0;
}
/*
4 10
1 2 3 4
5 6 7 8
1 2 3 4
*/

猜你喜欢

转载自www.cnblogs.com/R-Q-R-Q/p/12766270.html