题目大意:

给定一棵严格的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. 值类型和引用类型的区别,struct和class的区别

    C#值类型和引用类型 1.简单比较 值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中. 值类型(value type):byte,short,int,long,floa ...

  2. Spark Streaming简介及原理

    简介: SparkStreaming是一套框架. SparkStreaming是Spark核心API的一个扩展,可以实现高吞吐量的,具备容错机制的实时流数据处理. 支持多种数据源获取数据: Spark ...

  3. react-创建react元素

    前言 react 元素,即JSX语法. const Nav, Profile; // 输入(JSX): const app = <Nav color="blue">&l ...

  4. http 概念

    什么是回调? 什么是同步/异步? 什么是I/O? 什么是单线/多线程? 什么是阻塞/非阻塞? 什么是事件? 什么是事件驱动? 什么是基于事件驱动的回调? 什么是事件循环?

  5. Linux内核分析第四章 读书笔记

    Linux内核分析第四章 读书笔记 第一部分--进程调度 进程调度:操作系统规定下的进程选取模式 面临问题:多任务选择问题 多任务操作系统就是能同时并发地交互执行多个进程的操作系统,在单处理器机器上这 ...

  6. 2017-2018-2 1723《程序设计与数据结构》实验四 & 实验五 & 课程总结 总结

    作业地址 实验四作业:https://edu.cnblogs.com/campus/besti/CS-IMIS-1723/homework/1943 提交情况如图: 实验五作业:https://edu ...

  7. [转帖]台积电近10万片晶圆报废,但7nm工艺将成2019营收主力

    台积电近10万片晶圆报废,但7nm工艺将成2019营收主力 2019年02月18日 13:19 1784 次阅读 稿源:Expreview超能网 0 条评论 https://www.cnbeta.co ...

  8. lsof 查看端口占用的进程ID

    1. nohup execute >/dev/null 2>&1 & 提交了一个后台jobs 2. 然后查看一下 哪个进程正在用 3. yum 安装lsof yum ins ...

  9. kali linux升级

    自己使用的是2017.2 版本的kali linux 想着升级一下 里面的包 比如msf 等 但是执行 msfupdate时提示 root@kali201702:~# msfupdate msfupd ...

  10. lamp下mysql安全加固

    lamp下mysql安全加固 1.修改root用户口令,删除空口令 缺省安装的MySQL的root用户是空密码的,为了安全起见,必须修改为强密码,所谓的强密码,至少8位,由字母.数字和符号组成的不规律 ...