NOI2009 二叉查找树 【区间dp】

【NOI2009】二叉查找树

【问题描述】

  已知一棵特殊的二叉查找树。根据定义,该二叉查找树中每个结点的数据值
都比它左子树结点的数据值大,而比它右子树结点的数据值小。
另一方面,这棵查找树中每个结点都有一个权值,每个结点的权值都比它的
儿子结点的权值要小。
已知树中所有结点的数据值各不相同;所有结点的权值也各不相同。这时可
得出这样一个有趣的结论:如果能够确定树中每个结点的数据值和权值,那么树
的形态便可以唯一确定。因为这样的一棵树可以看成是按照权值从小到大顺序插
入结点所得到的、按照数据值排序的二叉查找树。
一个结点在树中的深度定义为它到树根的距离加 1。因此树的根结点的深度
为 1。
  每个结点除了数据值和权值以外,还有一个访问频度。我们定义一个结点在
树中的访问代价为它的访问频度乘以它在树中的深度。整棵树的访问代价定义为
所有结点在树中的访问代价之和。
  现在给定每个结点的数据值、 权值和访问频度, 你可以根据需要修改某些
结点的权值,但每次修改你会付出 K 的额外修改代价。你可以把结点的权值改
为任何实数,但是修改后所有结点的权值必须仍保持互不相同。现在你要解决的
问题是, 整棵树的访问代价与额外修改代价的和最小是多少?
【输入格式】
输入文件为 treapmod.in。
输入文件第一行包含两个正整数 N 和 K。 N 为结点的个数, K 为每次
修改所需的额外修改代价。
接下来一行包含 N 个非负整数,是每个结点的数据值。
再接下来一行包含 N 个非负整数,是每个结点的权值。
再接下来一行包含 N 个非负整数,是每个结点的访问频度。
所有的数据值、权值、访问频度均不超过 400000。每两个数之间都有一个
空格分隔,且行尾没有空格。
【输出格式】
输出文件为 treapmod.out。
输出文件只有一个数字,即你所能得到的整棵树的访问代价与额外修改代价
之和的最小值。
【样例 1 输入】
4 10
1 2 3 4
1 2 3 4
1 2 3 4
【样例 1 输出】
29
第 6 页 共 8 页
【样例 1 说明】
输入的原图是左图,它的访问代价是 1×1+2×2+3×3+4×4=30。最佳的修改方
案 是 把 输 入 中 的 第 3 个 结 点 的 权 值 改 成 0 , 得 到 右 图 ,
访 问 代 价 是 1×2+2×3+3×1+4×2=19,加上额外修改代价 10,一共是 29。
【子任务】
40%的数据满足 N ≤ 30;
70%的数据满足 N ≤ 50;
100%的数据满足 N ≤ 70, 1 ≤ K ≤ 30000000。

【题解】

  数据值已经确定了,那么这棵树的中序遍历就已经确定了,我们相当于改变权值来改变深度。

  而权值可以是任意实数,但是却和答案无关,我们完全可以里离散化一下。

  那么我们设dp[i][j][w]表示以区间[i,j]内的节点为树,区间内的点都>=w的方案总数。

  我们通过枚举区间内的根k,可以写出转移方程(sum(i,j)表示[i,j]的访问频率和)。

  当k>=w时,我们就可以把k之间弄成根,dp[i][j][w]=min(dp[i][k-1][wk]+dp[k+1][j][wk]+sum(i,j));

  也可以把k的权值修改成w,dp[i][j][w]=min(dp[i][k-1][w]+dp[k+1][w]+K+sum(i,j));

  因为如果之间dp偏序关系不太明确,在实现时可以使用记忆化搜索。(表示一开始直接dp调了一个上午没调出来)

【Code】

  

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
#define REP(i,a,b) for(register int i=(a),_end_=(b);i<=_end_;i++)
#define DREP(i,a,b) for(register int i=(a),_end_=(b);i>=_end_;i--)
#define EREP(i,a) for(register int i=start[(a)];i;i=e[i].next)
inline int read()
{
    int sum=0,p=1;char ch=getchar();
    while(!((‘0‘<=ch && ch<=‘9‘) || ch==‘-‘))ch=getchar();
    if(ch==‘-‘)p=-1,ch=getchar();
    while(‘0‘<=ch && ch<=‘9‘)sum=sum*10+ch-48,ch=getchar();
    return sum*p;
}

const int maxn=100;

int n,K;

struct node {
    int a,b,c;
};
node g[maxn];

bool cmp (const node x,const node y)
{
    return x.a<y.a;
}

int Sum(int x,int y)
{
    return g[y].c-g[x-1].c;
}

void init()
{
    int s[maxn];
    n=read();K=read();
    REP(i,1,n)g[i].a=read();
    REP(i,1,n)g[i].b=read(),s[i]=g[i].b;
    REP(i,1,n)g[i].c=read();
    sort(g+1,g+n+1,cmp);
    sort(s+1,s+n+1);
    int cnt=unique(s+1,s+n+1)-(s+1);
    REP(i,1,n)
    {
        REP(j,1,cnt)
        {
            if(g[i].b==s[j]){g[i].b=j;break;}
        }
    }
    REP(i,1,n)g[i].c+=g[i-1].c;
}

ll dp[maxn][maxn][maxn];

void chkmin(ll &a,ll b)
{
    if(a>b)a=b;
}
#define inf 0x7ffffffffff
ll dfs(int l,int r,int k)
{
    if(l>r)return 0;
    if(~dp[l][r][k])return dp[l][r][k];
    ll ans=inf;
    REP(i,l,r)
    {
        chkmin(ans,dfs(l,i-1,k)+dfs(i+1,r,k)+K);
        if(g[i].b>=k)chkmin(ans,dfs(l,i-1,g[i].b)+dfs(i+1,r,g[i].b));
    }
    return dp[l][r][k]=ans+Sum(l,r);
}

void doing()
{
    memset(dp,-1,sizeof(dp));
    //REP(i,1,n)dp[i][i][g[i].b]=Sum(i,i);
    /*REP(len,1,n-1)
    {
        REP(i,1,n-len)
        {
            int j=i+len;
            REP(k,1,Min(i,j))
            {
                REP(l,i,j)
                {
                    int Mn=Min(i,j),z=0;
                    if(Mn==g[l].b)z=0;else z=K;
                    chkmin(dp[i][j][k],dp[i][l-1][Mn]+dp[l+1][j][Mn]+z+Sum(i,j));
                }
            }
        }
        }*/
    cout<<dfs(1,n,1)<<endl;
}
int main()
{
    init();
    doing();
    return 0;
}
时间: 2024-08-07 17:00:06

NOI2009 二叉查找树 【区间dp】的相关文章

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

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

uva 10003 Cutting Sticks 简单区间dp

// uva 10003 Cutting Sticks 区间dp // 经典的区间dp // dp(i,j)表示切割小木棍i-j所需要的最小花费 // 则状态转移为dp(i,j) = min{dp(i,k) + dp(k,j) + a[j]-a[i]) // 其中k>i && k<j // a[j] - a[i] 为第一刀切割的代价 // a[0] = 0,a[n+1] = L; // dp数组初始化的时候dp[i][i+1]的值为 0,这表示 // 每一段都已经是切割了的,不

黑书例题 Fight Club 区间DP

题目可以在bnuoj.soj等OJ上找到. 题意: 不超过40个人站成一圈,只能和两边的人对战.给出任意两人对战的输赢,对于每一个人,输出是否可能是最后的胜者. 分析: 首先序列扩展成2倍,破环成链. dp[i][j]表示i和j能够相遇对打,那么dp[i][i+n]为真代表可以成为最后胜者. 枚举中间的k,若i和j都能和k相遇,且i和j至少一人能打赢k,那么i和j可以相遇. 复杂度o(n^3) 1 #include<cstdio> 2 #include<cstring> 3 usi

算法复习——区间dp

感觉对区间dp也不好说些什么直接照搬讲义了2333 例题: 1.引水入城(洛谷1514) 这道题先开始看不出来到底和区间dp有什么卵关系···· 首先肯定是bfs暴力判一判可以覆盖到哪些城市····无解直接输出···有解得话就要想想了···· 这道题关键是要发现··如果一个蓄水池所在城市可以覆盖到一些沙漠城市···那么这些沙漠城市肯定是一段····不然假设有一个城市是断开的而两边都被同一个蓄水池流出的水覆盖,这个城市四周的城市都肯定比它矮···(不理解举个反例吧···反正我举不出来)···然后就

合并石子 区间dp水题

合并石子 链接: nyoj 737 描述: 有N堆石子排成一排,每堆石子有一定的数量.现要将N堆石子并成为一堆.合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆.求出总的代价最小值. tags:最基本的区间dp,这题范围小,如果n大一些,还是要加个平行四边行优化. #include<iostream> #include<cstdio> #include<cstdlib> #include<cstring&g

Luogu P2734 游戏 A Game 区间DP

P2734 游戏 A Game 题目背景 有如下一个双人游戏:N(2 <= N <= 100)个正整数的序列放在一个游戏平台上,游戏由玩家1开始,两人轮流从序列的任意一端取一个数,取数后该数字被去掉并累加到本玩家的得分中,当数取尽时,游戏结束.以最终得分多者为胜. 题目描述 编一个执行最优策略的程序,最优策略就是使玩家在与最好的对手对弈时,能得到的在当前情况下最大的可能的总分的策略.你的程序要始终为第二位玩家执行最优策略. 输入输出格式 输入格式: 第一行: 正整数N, 表示序列中正整数的个数

HDU-4283 You Are the One (区间DP)

Problem Description The TV shows such as You Are the One has been very popular. In order to meet the need of boys who are still single, TJUT hold the show itself. The show is hold in the Small hall, so it attract a lot of boys and girls. Now there ar

lightoj1031_区间dp

题目链接:http://lightoj.com/volume_showproblem.php?problem=1031 题目描述: 给出一个数列,两人轮流取数, 取完结束.每次可以取好多个数,但是只能从首或者尾为起点取连续的若干个.问最后两者取数和的绝对值最大为多少? 区间dp: 这道题我是在看了几份阶梯报告之后才想通的,现在想想很符合动态规划的要求 d(i, j)表示取数的人在数组i 到 j中能取的的最大值,然后中间枚举分割点, ans = max(ans, sum[k]-sum[i-1]-d

lightoj1025_区间dp

题目链接:http://lightoj.com/volume_showproblem.php?problem=1025 题目描述: 给出一个字符串,可以任意删除位置的字符,也可以删除任意多个.问能组成多少个回文串? 解题思路: 自从开始学dp,感觉自己智商一直处于离线状态.席八啊啊啊啊啊啊!今天随机到这个题目,看了好久竟然没有看出来是区间DP.知道是区间DP后立马感觉明白. 情景设定 dp[l][r] 表示 区间 [l, r] 内的回文串数目. 状态转移:dp[l][r] = dp[l][r-1

POJ 2955 Brackets (区间DP)

题意:给定一个序列,问你最多有多少个合法的括号. 析:区间DP,dp[i][j] 表示在 第 i 到 第 j 区间内最多有多少个合法的括号. 代码如下: #pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #include <string> #include <cstdlib> #include <cmath> #include <ios