【BZOJ1564】【NOI2009】二叉查找树(动态规划)
题面
已知一棵特殊的二叉查找树。根据定义,该二叉查找树中每个结点的数据值都比它左儿子结点的数据值大,而比它右儿子结点的数据值小。
另一方面,这棵查找树中每个结点都有一个权值,每个结点的权值都比它的儿子结点的权值要小。
已知树中所有结点的数据值各不相同;所有结点的权值也各不相同。这时可得出这样一个有趣的结论:如果能够确定树中每个结点的数据值和权值,那么树的形态便可以唯一确定。因为这样的一棵树可以看成是按照权值从小到大顺序插入结点所得到的、按照数据值排序的二叉查找树。
一个结点在树中的深度定义为它到树根的距离加1。因此树的根结点的深度为1。
每个结点除了数据值和权值以外,还有一个访问频度。我们定义一个结点在树中的访问代价为它的访问频度乘以它在树中的深度。整棵树的访问代价定义为所有结点在树中的访问代价之和。
现在给定每个结点的数据值、权值和访问频度,你可以根据需要修改某些结点的权值,但每次修改你会付出K的额外修改代价。你可以把结点的权值改为任何实数,但是修改后所有结点的权值必须仍保持互不相同。现在你要解决的问题是,整棵树的访问代价与额外修改代价的和最小是多少?
输入输出格式
输入格式:
输入文件中的第一行为两个正整数N,K。其中:N表示结点的个数,K表示每次修改所需的额外修改代价。
接下来的一行为N个非负整数,表示每个结点的数据值。
再接下来的一行为N个非负整数,表示每个结点的权值。
再接下来的一行为N个非负整数,表示每个结点的访问频度。
其中:所有的数据值、权值、访问频度均不超过400000。每两个数之间都有一个空格分隔,且行尾没有空格。
输出格式:
输出文件中仅一行为一个数,即你所能得到的整棵树的访问代价与额外修改代价之和的最小值。
题解
我们知道,二叉查找树在拍平(也就是中序遍历)之后得到的序列是有序的
所以一般这类问题就可以考虑区间\(dp\)了。
因此,我们的第一想法设\(f[i][j]\)表示把\(i..j\)这一段构好树得到的最小代价。
首先我们不考虑权值的情况。
那么转移的时候,枚举一下树根,左右侧分别构树,作为当前根节点的儿子
同时所有点的深度都会加一,再额外计算一下这个值就可以了。
现在权值是可以修改的,如果自己想想,发现这棵二叉查找树是一个\(Treap\)
同时对于权值维护的是一个小根堆。另外,权值的大小没有意义,可以直接离散。
因此,在枚举根节点的时候,左右儿子对于当前根节点的影响一定是这段区间内权值的最小值所带来的影响。
所以,给状态加一维,设\(f[i][j][k]\)表示当前区间是\(i..j\),其中最小权值是\(k\)的二叉搜索树的最小代价。
考虑如何转移
对于区间,显然要先枚举当前的根节点,那么根节点一定就是最小权值,所以还枚举根节点的权值。
枚举左右子树的根节点的权值,这个权值一定要大于当前根节点的权值。
额外的费用只有是否修改当前根节点的权值。
所以,转移就是
\(f[i][j][k]=min(f[i][p-1][n]+f[p+1][j][m]+\sum_{l\in[i,j]}s[l]+[v[i]==k]·K)\)
这样复杂度是\(O(N^6)\)的(因为还要枚举左右子树的根节点的权值)
但是如果我们把状态的\(k\)改为所有值都不小于\(k\)的最小代价
这样就没有问题啦。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 75
inline int read()
{
RG int x=0,t=1;RG char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
struct Node{int v,key,s;}t[MAX];
bool operator<(Node a,Node b){return a.v<b.v;}
int S[MAX],n,K;
int f[MAX][MAX][MAX];
int main()
{
n=read();K=read();
for(int i=1;i<=n;++i)t[i].v=read();
for(int i=1;i<=n;++i)S[i]=t[i].key=read();
for(int i=1;i<=n;++i)t[i].s=read();
sort(&S[1],&S[n+1]);int len=unique(&S[1],&S[n+1])-S-1;
for(int i=1;i<=n;++i)t[i].key=lower_bound(&S[1],&S[len+1],t[i].key)-S;
sort(&t[1],&t[n+1]);
for(int i=1;i<=n;++i)S[i]=t[i].s+S[i-1];
memset(f,63,sizeof(f));
for(int i=1;i<=n+1;++i)
for(int j=0;j<=n;++j)
f[i][i-1][j]=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
f[i][i][j]=t[i].s+(t[i].key!=j)*K;
for(int k=n;k;--k)
for(int l=1;l<=n;++l)
for(int i=1,j=i+l-1;j<=n;++i,++j)
for(int p=i;p<=j;++p)
{
f[i][j][k]=min(f[i][j][k],f[i][p-1][k]+f[p+1][j][k]+S[j]-S[i-1]+K);
if(k<=t[p].key)f[i][j][k]=min(f[i][j][k],f[i][p-1][t[p].key]+f[p+1][j][t[p].key]+S[j]-S[i-1]);
}
printf("%d\n",f[1][n][1]);
return 0;
}