二叉树问题(区间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