[NOI2009] 二叉查找树 题解
[NOI2009] 二叉查找树 题解
题解:
通过题目中,对树的描述:按照权值从小到大顺序插入结点所得到的按照数据值排序的二叉查找树,可以想到这是一棵 treap。改变权值相当于 treap 的左旋右旋,通过旋转会使结点的深度发生改变,从而影响最终的答案。
根据 treap 的性质,因为数据值不变,那么树的中序遍历也不会变, 而二叉搜索树的的中序遍历,就是数据值从小到大排序的结果。
接下来就可以 dp 了。
因为权值可以取任意的实数,而且最后的答案与权值无关,方便之后的处理可以将权值离散化。
状态:设 \(f[i][j][k]\) 表示 \(i\) 到 \(j\) 节点组成了一棵树,其中权值都 \(>= k\), 所需要的代价为多少。
(第三维的权值是有离散化的,看代码),答案就是 \(f[1][n][1]\)。
转移:从 \(i\) 到 \(j\) 枚举一个 \(t\),假设让 \(t\) 来做根节点, \(t\) 结点的权值都比它的儿子结点的权值要小 (题目性质)。
接下来考虑 \(t\) 这个节点的权值是否需要改变。
如果 \(t\) 结点已经 $ >= k$ 了,不需要修改:
\(f[i][j][k] = min(f[i][j][k], f[i][t - 1][t的权值] + f[t + 1][j][t的权值] + i 到 j的访问频率之和)\)
关于这个转移方程:
-
虽然题目中说,“所有结点的权值必须仍保持互不相同”,但权值为实数类型,两个权值可以无限接近而不相同,所以这个条件并无约束作用,转移方程中写的是“t的权值”。
-
转移方程之中“i 到 j的访问频率之和”,计算的是访问代价。
访问代价 = 深度 $\times $ 访问频率。当我们一层层计算时,每次都会将当前子树内的访问频率加一遍,累积起来就是访问代价。
如果要修改:
\(f[i][j][k] = min(f[i][j][k], f[i][t - 1][k] + f[t + 1][j][k] + i 到 j的访问频率之和)\)
/*
Date:2021.8.2
Source:luogu 1864
konwledge:对treap的理解 + dp
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define orz cout << "AK IOI"
using namespace std;
const int maxn = 80;
const int maxm = 4e5 + 10;
inline int read()
{
int f = 0, x = 0; char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) x = x * 10 + (ch ^ 48), ch = getchar();
return f ? -x : x;
}
inline void print(int X)
{
if(X < 0) {X = ~(X - 1); putchar('-');}
if(X > 9) print(X / 10);
putchar(X % 10 + '0');
}
int n, K, val[maxn], sum[maxn], f[maxn][maxn][maxn];
struct node{
int num, val, sum;
}a[maxn];
bool cmp(node a, node b)
{
return a.num < b.num;
}
int main()
{
n = read(), K = read();
for(int i = 1; i <= n; i++) a[i].num = read();
for(int i = 1; i <= n; i++) val[i] = a[i].val = read();
for(int i = 1; i <= n; i++) a[i].sum = read();
sort(a + 1, a + n + 1, cmp);
sort(val + 1, val + n + 1);
for(int i = 1; i <= n; i++)
{
a[i].val = lower_bound(val + 1, val + n + 1, a[i].val) - val;//离散化
sum[i] = sum[i - 1] + a[i].sum;
}
memset(f, 63, sizeof f);
for(int i = 1; i <= n + 1; i++)
for(int j = 1; j <= n; j++) f[i][i - 1][j] = 0;
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(a[t].val >= k)
f[i][j][k] = min(f[i][j][k], f[i][t - 1][a[t].val] + f[t + 1][j][a[t].val] + sum[j] - sum[i - 1]);
f[i][j][k] = min(f[i][j][k], f[i][t - 1][k] + f[t + 1][j][k] + K + sum[j] - sum[i - 1]);
}
printf("%d", f[1][n][1]);
return 0;
}
感谢 https://www.luogu.com.cn/blog/wangwangwangwangwang/solution-p1864