二叉树问题(区间DP好题)

二叉树问题

时间限制: 1 Sec  内存限制: 128 MB

题目描述

Petya Bulochkin很幸运:他得到了一份在“Macrohard”公司的工作。他想要展现他的才华,所以他要把他第一份工作做得尽可能好。这个任务是写一个搜索引擎。Petya知道一系列的整数A1,A2,……,Ak(k<=300, 1<=Ai<=10000000, Ai 都不相同)我们把这些数称作关键字。这个引擎应该要能回答这样的问题:“那里是不是有一个关键字是S?”我们已经知道,S是1到n(1<=n<=10000000)中任何一个整数。Petya决定使用排序二叉树来解决这个问题。
我们把比较的次数称作费用C。例如,对于第三棵二叉树,我们有对于每一个S的查找费用:
S 1 2 3 4 5 6 7 8 9 10 11
C 2 3 3 3 3 3 1 2 2 2 2
我们把对于s=1~n的费用和称作二叉树的费用,例如,第三棵二叉树的费用是2+3+3+3+3+3+1+2+2+2+2=26
我们的任务是对于关键字A1~Ak,写出由这些关键字形成的二叉树中的最小费用。

输入

第一行是n,第二行是k,下面k行中第i行是Ai

输出

输出文件仅有一行包含一个整数表示要求的最小费用。

样例输入

10 4
9 3 7 4

样例输出

22

提示

这个最小费用的二叉树指的是:

题解:
先拿样例来说,根据二叉搜索树的左小右大的性质,4~7都是查找2次,7~9都是查找3次,不难发现,查找次数相同的数都是以区间的形式存在的。
因此判断这道题是区间DP,将n个数从小到大排序即可。
确定了思路,现在让我们来看怎么定义状态,定义状态是这道题的难点,本人一开始简单的认为f[i][j]表示在区间i~j中查找数a[i]~a[j]的最小次数,发现后效性不可避免。后来听了点大佬的教导,其实正确的状态定义应该为f[i][j]表示在区间i~j中查找a[i-1]+1~a[j+1]-1的最小次数。(是不是很巧妙?)
动规方程很简单f[j][i+j-1]=min(f[j][i+j-1],f[j][k-1]+(a[i+j]-a[j-1]-1));
AC代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<stack>
#include<ctime>
#include<vector>
using namespace std;
int n,m;
int a[501],f[501][501];
int main()
{
    int i,j,k;
    memset(f,127/3,sizeof(f));
    scanf("%d",&m);
    scanf("%d",&n);
    for(i=1;i<=n;i++)
        scanf("%d",&a[i]);
    sort(a+1,a+n+1);
    a[0]=0;a[n+1]=m+1;
    for(i=1;i<=n;i++)
        f[i][i]=a[i+1]-a[i-1]-1;
    for(i=2;i<=n;i++)
    {
        for(j=1;j<=n-i+1;j++)
        {
            for(k=j;k<=i+j-1;k++)
            {
                if(k==j)f[j][i+j-1]=min(f[j][i+j-1],f[k+1][i+j-1]+(a[i+j]-a[j-1]-1));
                else if(k==i+j-1)f[j][i+j-1]=min(f[j][i+j-1],f[j][k-1]+(a[i+j]-a[j-1]-1));
                else f[j][i+j-1]=min(f[j][i+j-1],f[j][k-1]+f[k+1][i+j-1]+(a[i+j]-a[j-1]-1));
            }
        }
    }
    cout<<f[1][n];
    return 0;
}
特别感谢,@SilverWolf
原文地址:https://www.cnblogs.com/huangdalaofighting/p/7002005.html