「SCOI2006」zh_tree
题目限制
- 内存限制:250.00MB
- 时间限制:1.00s
- 标准输入输出
题目知识点
- 思维
- 动态规划
d
p
dp
dp
- 区间 d p dp dp
题目来源
题目
题目背景
张老师根据自己工作的需要,设计了一种特殊的二叉搜索树
题目描述
他把这种二叉树起名为 zh_tree,对于具有
n
n
n 个节点的 zh_tree,其中序遍历恰好为
(
1
、
2
、
3
、
⋅
⋅
⋅
、
n
)
(1、2、3、···、n)
(1、2、3、⋅⋅⋅、n),其中数字
1
、
2
、
3
、
⋅
⋅
⋅
、
n
1、2、3、···、n
1、2、3、⋅⋅⋅、n 是每个节点的编号
n
n
n 个结点恰好对应于一组学术论文中出现的
n
n
n 个不同的单词
第
i
i
i 个单词在该组论文中出现的次数记为
d
i
d_i
di,例如,
d
2
=
10
d_2 = 10
d2=10 表示第
2
2
2 个节点所对应的单词在该组论文中出现了
10
10
10 次
设该组论文中出现的单词总数为
S
S
S,即
S
=
d
1
+
d
2
+
⋅
⋅
⋅
+
d
n
=
∑
i
=
1
n
d
i
S = d_1 + d_2 + ··· + d_n = \sum _{i = 1} ^{n} d_i
S=d1+d2+⋅⋅⋅+dn=∑i=1ndi
记
f
i
=
d
i
S
f_i = \frac{d_i}{S}
fi=Sdi 为第
i
i
i 个单词在该组论文中出现的 概率(频率)
张老师把根节点深度规定为 0 0 0,如果第 i i i 个节点的深度为 r i r_i ri,则访问该节点的代价为 h i h_i hi, h i = k ( r i + 1 ) + c h_i = k(r_i + 1) + c hi=k(ri+1)+c,其中 k 、 c k、c k、c 为已知的常数 ( 0 < k , c ≤ 100 ) (0 < k, c \leq 100) (0<k,c≤100)
则 zh_tree 是满足
h
1
f
1
+
h
2
f
2
+
⋅
⋅
⋅
+
h
n
f
n
=
∑
i
=
1
n
h
i
f
i
h_1f_1 + h_2f_2 + ··· + h_nf_n = \sum _{i = 1} ^ {n} h_if_i
h1f1+h2f2+⋅⋅⋅+hnfn=∑i=1nhifi 最小的一棵二叉树
我们称上式为访问 zh_tree 的代价
请你根据已知的数据为张老师设计一棵 zh_tree
格式
输入格式
输入共
2
2
2 行:
对于第
1
1
1 行,
3
3
3 个用空格隔开的正数,
n
、
k
、
c
n、k、c
n、k、c。其中
n
<
30
n < 30
n<30,为整数;
k
、
c
k、c
k、c 为不超过
100
100
100 的正实数
对于第
2
2
2 行:
n
n
n 个用空格隔开的正整数,为每个单词出现的次数
d
i
(
d
i
<
200
)
d_i(d_i < 200)
di(di<200)
输出格式
输出一行:一个正实数,保留 3 3 3 位小数,表示访问 zh_tree 的最小代价
样例
样例输入
4 2 3.5
20 30 50 20
样例输出
7.000
提示
数据范围
对于 100 % 100\% 100% 的数据: 1 ≤ n < 30 1 \leq n < 30 1≤n<30, 0 < k 、 c ≤ 100 0 < k、c \leq 100 0<k、c≤100, d i < 200 d_i < 200 di<200
思路
对于每个节点的深度 r i r_i ri,由于 h i = k ( r i + 1 ) + c h_i = k(r_i + 1) + c hi=k(ri+1)+c,不妨可以把根节点的深度改为 1 1 1,这样就可以直接用 现在的 r i r_i ri 表示 原来的 r i + 1 r_i + 1 ri+1 了。
先把
∑
i
=
1
n
h
i
f
i
\sum\limits_{i = 1}^n h_i f_i
i=1∑nhifi 化简:
∑
i
=
1
n
(
h
i
×
f
i
)
=
∑
i
=
1
n
(
k
×
r
i
+
c
)
×
d
i
S
=
1
S
×
∑
i
=
1
n
(
k
×
r
i
+
c
)
×
d
i
=
1
S
×
∑
i
=
1
n
(
k
×
r
i
×
d
i
+
c
×
d
i
)
=
1
S
×
(
∑
i
=
1
n
k
×
r
i
×
d
i
+
∑
i
=
1
n
c
×
d
i
)
=
(
1
S
×
∑
i
=
1
n
k
×
r
i
×
d
i
)
+
(
1
S
×
c
×
∑
i
=
1
n
d
i
)
=
(
k
S
×
∑
i
=
1
n
r
i
×
d
i
)
+
(
1
S
×
c
×
S
)
=
k
×
∑
i
=
1
n
r
i
×
d
i
S
+
c
\begin{aligned} \sum\limits_{i = 1}^n (h_i \times f_i) & = \sum\limits_{i = 1}^n (k \times r_i + c) \times \dfrac {d_i} S \\ & = \dfrac 1 S \times \sum\limits_{i = 1} ^{n} (k \times r_i + c) \times d_i \\ & = \dfrac 1 S \times \sum\limits_{i = 1}^n (k \times r_i \times d_i + c \times d_i) \\ & = \dfrac 1 S \times (\sum\limits_{i = 1}^n k \times r_i \times d_i + \sum\limits_{i = 1}^n c \times d_i) \\ & = (\dfrac 1 S \times \sum\limits_{i = 1}^n k \times r_i \times d_i) + (\dfrac 1 S \times c \times \sum\limits_{i = 1} ^{n} d_i) \\ & = (\dfrac k S \times \sum\limits_{i = 1}^n r_i \times d_i) + (\dfrac 1 S \times c \times S) \\ & = \dfrac {k \times \sum\limits_{i = 1}^n r_i \times d_i}{S} + c \\ \end{aligned}
i=1∑n(hi×fi)=i=1∑n(k×ri+c)×Sdi=S1×i=1∑n(k×ri+c)×di=S1×i=1∑n(k×ri×di+c×di)=S1×(i=1∑nk×ri×di+i=1∑nc×di)=(S1×i=1∑nk×ri×di)+(S1×c×i=1∑ndi)=(Sk×i=1∑nri×di)+(S1×c×S)=Sk×i=1∑nri×di+c
题目要我们求 ∑ i = 1 n h i f i \sum\limits_{i = 1}^n h_i f_i i=1∑nhifi 的最小值,就可以转化成求 ∑ i = 1 n r i × d i \sum\limits_{i = 1}^n r_i \times d_i i=1∑nri×di 的最小值。
- 状态定义:设 f L , R f_{L, R} fL,R 表示:一棵二叉树的 中序遍历(左子树,根节点,右子树) 为 L ∼ R ( L , L + 1 , L + 2 , ⋯ , R ) L \sim R(L, L + 1, L + 2, \cdots, R) L∼R(L,L+1,L+2,⋯,R) 时 ∑ i = L R r i × d i \sum\limits_{i = L}^R r_i \times d_i i=L∑Rri×di 的最小值,最终的答案就是 k × f 1 , n S + c \dfrac{k \times f_{1, n}}{S} + c Sk×f1,n+c。
- 初始化: ∀ L = R = i , f L , R = d i \forall L = R = i, f_{L, R} = d_i ∀L=R=i,fL,R=di; ∀ L < R , f L , R = + ∞ \forall L < R, f_{L, R} = + \infty ∀L<R,fL,R=+∞。
- 状态转移:对于中序遍历为 L ∼ R L \sim R L∼R 的二叉树,枚举其根节点 t r ( L ≤ t r ≤ R ) tr(L \leq tr \leq R) tr(L≤tr≤R), f L , R = min t r = L R { f L , t r − 1 + f t r + 1 , R + ∑ i = L R d i } = min t r = L R { f L , t r − 1 + f t r + 1 , R } + ∑ i = L R d i f_{L, R} = \min\limits_{tr = L}^R \{ f_{L, tr - 1} + f_{tr + 1, R} + \sum\limits_{i = L}^{R} d_i \} = \min\limits_{tr = L}^R \{ f_{L, tr - 1} + f_{tr + 1, R} \} + \sum\limits_{i = L}^{R} d_i fL,R=tr=LminR{fL,tr−1+ftr+1,R+i=L∑Rdi}=tr=LminR{fL,tr−1+ftr+1,R}+i=L∑Rdi。
如果需要输出方案,可以在转移过程中记录下 f L , R f_{L, R} fL,R 取最小值时的根节点,最后递归即可。
转移时 ∑ i = L R d i \sum\limits_{i = L}^{R} d_i i=L∑Rdi 可以用前缀和作差,这样时间复杂度 O ( n 4 ) → O ( n 3 ) O(n ^ 4) \to O(n ^ 3) O(n4)→O(n3),不过都可以 AC \texttt{AC} AC。
代码
#include <cstdio>
#include <cstring>
#define Min(a, b) ((a) < (b) ? (a) : (b))
const int MAXN = 30;
int N, S;
int D[MAXN + 5];
int Pre[MAXN + 5]; // 前缀和优化
double K, C;
double dp[MAXN + 5][MAXN + 5];
int main()
{
memset(dp, 0x3f, sizeof(dp));
scanf("%d %lf %lf", &N, &K, &C);
for (int i = 1; i <= N; i++)
{
scanf("%d", &D[i]);
Pre[i] = S += D[i];
dp[i][i] = 1.0 * D[i];
}
for (int len = 2; len <= N; len++)
{
for (int i = 1, j = len; j <= N; i++, j++)
{
dp[i][j] = Min(dp[i][j - 1], dp[i + 1][j]);
for (int k = i + 1; k <= j - 1; k++)
dp[i][j] = Min(dp[i][j], dp[i][k - 1] + dp[k + 1][j]);
dp[i][j] += 1.0 * (Pre[j] - Pre[i - 1]);
}
}
printf("%.3lf\n", K * dp[1][N] / S + C);
return 0;
}