• [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的访问频率之和))

    关于这个转移方程:

    1. 虽然题目中说,“所有结点的权值必须仍保持互不相同”,但权值为实数类型,两个权值可以无限接近而不相同,所以这个条件并无约束作用,转移方程中写的是“t的权值”。

    2. 转移方程之中“i 到 j的访问频率之和”,计算的是访问代价。

      访问代价 = 深度 $ imes $​​ 访问频率。当我们一层层计算时,每次都会将当前子树内的访问频率加一遍,累积起来就是访问代价。

    如果要修改:

    (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

  • 相关阅读:
    函数库:静态库和动态库
    预处理
    共用体、大端小端的判断、枚举
    结构体内存对齐及大小的判断
    内存的管理方式
    指针的高级应用
    H5+css3属性随笔
    项目实战——仿360囧图
    利用css3的动画实现图片轮播
    了解HTML5大纲算法
  • 原文地址:https://www.cnblogs.com/yangchengcheng/p/15089004.html
Copyright © 2020-2023  润新知