题目大意:

给定一棵严格的treap,父亲节点的优先级必然小于儿子节点的。权值按照二叉树的定义,左儿子小于父亲小于右儿子。

深度从1开始定义,每个点除优先级、数值之外,还有一个访问频度。

访问频度所产生的代价是:访问频度*该点深度(这和事实相符)

可以用给定的k的代价,修改任意个点的优先级为任意实数(当然,修改优先级,树的形态,各点深度就可能变化了)

最终的总代价为:频度产生代价+修改代价。

最小化这个总代价。

N<=70,1<=K<=30000000

分析:

平衡树是一个动态的数据结构,难以抓住形态的变化,也不方便记录深度之类。所以必须抓住不变的量当做突破口。

不管平衡树怎么转,根据二叉树的定义,它的中序遍历一定是不变的。

所以我们可以找到这棵树的中序遍历,就把这棵树变成了一个静态的区间,只不过每个区间所代表的点的优先级可能会变。

发现,每一个连续的子区间,都对应treap的连续一部分。可以把小的区间先建树,再把大的区间用小的区间合并。我们合并的时候枚举的划分点,就是这部分treap的树根

区间DP顺理成章。

除了f[l][r]之外,为了维护优先级的关系,必然要再记录一维。

发现,只要根节点的优先级确定,子树的优先级的范围就确定了。

所以考虑记录根节点优先级。(这里优先级只考虑相对大小,而且范围又大,所以要离散化为1~n)

但是,朴素的f[l][r][w]中,w单单记录根节点优先级的话,由于子树所有大于w的都可以转移,还要for一遍。状态n^3,转移n^2,会爆。

所以,我们令f[l][r][w]表示,将l~r这段区间建成treap,其中根节点优先级大于等于w的最小代价。

根据枚举的根节点是否修改,可以设计转移方程是:

修改:

f[l][r][w]=min(f[l][r][w],f[l][k-1][w]+f[k+1][r][w]+K+sum[r]-sum[l-1])  ————其中,sum[i]表示,区间中,1~i的访问频度和

当划分点的优先级ch[k]大于w时,可以不修改。

f[l][r][w]=min(f[l][r][w],f[l][k-1][ch[k]]+f[k+1][r][ch[k]]+sum[r]-suim[l-1])

最后答案就是:f[1][n][1];

注意,o循环的时候,必须倒序!!因为ch[k]>=o时候,要从o更大的地方获取最小值,必须先把o较大的处理完。

代码1(未简化):

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=;
const ll inf=2e18;
ll f[N][N][N];
int n;
ll m;
int a[N];
int tot;
int w[N],p[N],d[N];
int prio[N];
ll sum[N];
int ch[N];//离散化后的优先级
bool cmp(int a,int b)
{
return w[a]<w[b];
}
int main()
{
scanf("%d%lld",&n,&m);
for(int i=;i<=n;i++) scanf("%d",&w[i]),a[i]=i;
for(int i=;i<=n;i++) scanf("%d",&p[i]),prio[i]=p[i];
for(int i=;i<=n;i++) scanf("%d",&d[i]);
sort(a+,a+n+,cmp);
sort(prio+,prio+n+);
for(int i=;i<=n;i++) ch[i]=lower_bound(prio+,prio+n+,p[a[i]])-prio;//离散化 for(int i=;i<=n;i++)
for(int j=;j<=n;j++)
for(int k=;k<=n;k++)
f[i][j][k]=inf;
for(int i=;i<=n;i++)
for(int k=n;k>=;k--)
{
if(k<=ch[i]) f[i][i][k]=d[a[i]];
else f[i][i][k]=d[a[i]]+m;//注意,k>ch的时候,不一定是+oo,可以通过修改改变
}//l=1的初值 for(int i=;i<=n;i++)
sum[i]=sum[i-]+d[a[i]];//前缀和
for(int l=;l<=n;l++)
for(int i=;i<=n;i++)
{
int j=l+i-;
if(j>n) break;
if(l==)//长度为二的时候,只能二并一
{
for(int o=n;o>=;o--)
{
f[i][j][o]=min(f[i][j][o],f[i][i][o]+m+sum[j]-sum[i-]);
f[i][j][o]=min(f[i][j][o],f[j][j][o]+m+sum[j]-sum[i-]);
if(ch[i]>=o) f[i][j][o]=min(f[i][j][o],f[i][i][ch[i]]+f[j][j][ch[i]]+sum[j]-sum[i-]-d[a[i]]);
if(ch[j]>=o) f[i][j][o]=min(f[i][j][o],f[i][i][ch[j]]+f[j][j][ch[j]]+sum[j]-sum[i-]-d[a[j]]);
}
}
else{
for(int o=n;o>=;o--)
for(int k=i;k<=j;k++)
{
if(k==i)//k在端点处,只能用端点和右边所有部分合并
{
f[i][j][o]=min(f[i][j][o],f[i+][j][o]+m+sum[j]-sum[i-]);
if(ch[i]>=o) f[i][j][o]=min(f[i][j][o],f[i][i][ch[i]]+f[i+][j][ch[i]]+sum[j]-sum[i-]-d[a[i]]);
}
else if(k==j)//同理
{
f[i][j][o]=min(f[i][j][o],f[i][j-][o]+m+sum[j]-sum[i-]);
if(ch[j]>=o) f[i][j][o]=min(f[i][j][o],f[i][j-][ch[j]]+f[j][j][ch[j]]+sum[j]-sum[i-]-d[a[j]]);
}
else{//正宗转移方程
f[i][j][o]=min(f[i][j][o],f[i][k-][o]+f[k+][j][o]+m+sum[j]-sum[i-]);
if(ch[k]>=o) f[i][j][o]=min(f[i][j][o],f[i][k-][ch[k]]+f[k+][j][ch[k]]+sum[j]-sum[i-]);
}
}
}
}
printf("%lld",f[][n][]);
return ;
}

太恶心了。为了保证l<=r,做出了巨大的讨论。

其实不用这么麻烦,只要让l>r的时候,赋值为0就好,相当于不存在。根本不影响答案。

代码2(化简)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=;
const ll inf=2e18;
ll f[N][N][N];
int n;
ll m;
int a[N];
int tot;
int w[N],p[N],d[N];
int prio[N];
ll sum[N];
int ch[N];//离散化后的优先级
bool cmp(int a,int b)
{
return w[a]<w[b];
}
int main()
{
scanf("%d%lld",&n,&m);
for(int i=;i<=n;i++) scanf("%d",&w[i]),a[i]=i;
for(int i=;i<=n;i++) scanf("%d",&p[i]),prio[i]=p[i];
for(int i=;i<=n;i++) scanf("%d",&d[i]);
sort(a+,a+n+,cmp);
sort(prio+,prio+n+);
for(int i=;i<=n;i++) ch[i]=lower_bound(prio+,prio+n+,p[a[i]])-prio; for(int i=;i<=n;i++)
for(int j=i;j<=n;j++)
for(int o=;o<=n;o++)
f[i][j][o]=inf;
for(int i=;i<=n;i++)
for(int o=;o<=n;o++)
f[i][i-][o]=;//其实这步不需要,因为上面就没有给它赋值,只是在这里强调一下。 for(int i=;i<=n;i++)
sum[i]=sum[i-]+d[a[i]];
for(int o=n;o>=;o--)
for(int i=n;i>=;i--)
for(int j=i;j<=n;j++)
for(int k=i;k<=j;k++)
{
f[i][j][o]=min(f[i][j][o],f[i][k-][o]+f[k+][j][o]+m+sum[j]-sum[i-]);
if(ch[k]>=o) f[i][j][o]=min(f[i][j][o],f[i][k-][ch[k]]+f[k+][j][ch[k]]+sum[j]-sum[i-]);
}//不放心,可以考虑代入长度小于等于2的情况。0的作用就出来了。
//连初始化l=1都省了。
printf("%lld",f[][n][]);
return ;
}

总结:

1.对于琢磨不透的变化,一定有不变的东西。一定要抓住其中的不变量,作为突破口。
2.循环顺序要注意,一个是不能有后效性,一个是要保证能影响到这个状态的所有状态都处理完了。

3.注意考虑清楚所有可能转移的方式。

[NOI2009]二叉查找树的更多相关文章

  1. BZOJ 1564: [NOI2009]二叉查找树( dp )

    树的中序遍历是唯一的. 按照数据值处理出中序遍历后, dp(l, r, v)表示[l, r]组成的树, 树的所有节点的权值≥v的最小代价(离散化权值). 枚举m为根(p表示访问频率): 修改m的权值 ...

  2. bzoj 1564 [NOI2009]二叉查找树 区间DP

    [NOI2009]二叉查找树 Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 906  Solved: 630[Submit][Status][Discu ...

  3. [BZOJ1564][NOI2009]二叉查找树 树形dp 区间dp

    1564: [NOI2009]二叉查找树 Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 879  Solved: 612[Submit][Status] ...

  4. P1864 [NOI2009]二叉查找树

    链接P1864 [NOI2009]二叉查找树 这题还是蛮难的--是我菜. 题目描述中的一大堆其实就是在描述\(treap.\),考虑\(treap\)的一些性质: 首先不管怎么转,中序遍历是确定的,所 ...

  5. NOI2009 二叉查找树 【区间dp】

    [NOI2009]二叉查找树 [问题描述] 已知一棵特殊的二叉查找树.根据定义,该二叉查找树中每个结点的数据值都比它左子树结点的数据值大,而比它右子树结点的数据值小.另一方面,这棵查找树中每个结点都有 ...

  6. BZOJ 1564 :[NOI2009]二叉查找树(树型DP)

    二叉查找树 [题目描述] 已知一棵特殊的二叉查找树.根据定义,该二叉查找树中每个结点的数据值都比它左儿子结点的数据值大,而比它右儿子结点的数据值小. 另一方面,这棵查找树中每个结点都有一个权值,每个结 ...

  7. [洛谷P1864] NOI2009 二叉查找树

    问题描述 已知一棵特殊的二叉查找树.根据定义,该二叉查找树中每个结点的数据值都比它左儿子结点的数据值大,而比它右儿子结点的数据值小. 另一方面,这棵查找树中每个结点都有一个权值,每个结点的权值都比它的 ...

  8. 洛谷$P1864\ [NOI2009]$二叉查找树 区间$dp$

    正解:区间$dp$ 解题报告: 传送门$QwQ$ 首先根据二叉查找树的定义可知,数据确定了,这棵树的中序遍历就已经改变了,唯一能改变的就是通过改变权值从而改变结点的深度. 发现这里权值的值没有意义,所 ...

  9. BZOJ 1564: [NOI2009]二叉查找树

    链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1564 Description Input Output 只有一个数字,即你所能得到的整棵树的访 ...

  10. [BZOJ1564][NOI2009]二叉查找树(区间DP)

    题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1564 分析: 首先因为每个点的数据值不变,所以无论树的形态如何变,树的中序遍历肯定不变 ...

随机推荐

  1. Git push 时如何避免出现 "Merge branch 'master' of ..."

    在使用 Git 的进行代码版本控制的时候,往往会发现在 log 中出现 "Merge branch 'master' of ..." 这句话,如下图所示.日志中记录的一般为开发过程 ...

  2. MariaDB 安装与启动 过程记录

    1. 安装之前的准备工作 rpm -qa |grep mysql rpm -qa |grep mariadb 按照查出来的软件包使用  yum remove  全部卸载,当然也可以 yum remov ...

  3. Linux下对文件进行加密备份的操作记录

    由于公司之前在阿里云上购买了一些机器,后续IDC建设好后,又将线上业务从阿里云上迁移到IDC机器上了,为了不浪费阿里云上的这几台机器资源,打算将这些机器做成IP SAN共享存储,然后作为IDC数据的一 ...

  4. Linux下selinux简单梳理

    在linux环境下执行某些程序时,偶尔会遇到来一个关于selinux的强制模式不可执行的情况,这种情况下需要关闭selinux或者将enforcing改为permissive模式后才能进行执行.sel ...

  5. MySQL针对Swap分区的运维注意点

    Linux有很多很好的内存.IO调度机制,但是并不会适用于所有场景.对于运维人员来说,Linux比较让人头疼的一个地方是:它不会因为MySQL很重要就避免将分配给MySQL的地址空间映射到swap上. ...

  6. Charles使用详解

    前言: Charles是在 Mac 下常用的网络封包截取工具,在做移动开发时,我们为了调试与服务器端的网络通讯协议,常常需要截取网络封包来分析. 一.主界面介绍       二.网页抓包 启动 Cha ...

  7. vue项目环境搭建

    安装node.js $ npm install -g vue-cli $ vue init webpack my-project ?Project name ?Project description ...

  8. 8-Python3从入门到实战—基础之数据类型(集合-Sets)

    Python从入门到实战系列--目录 集合的定义 集合(set)和字典类似,也是一组key的集合,但不存储value:由于key不能重复,所以,在set中,没有重复的key. 创建一个set,需要提供 ...

  9. 在Windows命令行中编译运行C/C++程序

    此处运行环境是在Windos下,运行cmd命令进入DOS界面 现在有一段简单C++代码(文件名为 demo.cpp),用于计算a*b的值 #include<iostream> using ...

  10. XSS编码与绕过

     XSS编码与绕过 0x00 背景 对于了解web安全的朋友来说,都知道XSS这种漏洞,其危害性不用强调了.一般对于该漏洞的防护有两个思路:一是过滤敏感字符,诸如[<,>,script,' ...