您的位置:首页 > 编程语言 > C语言/C++

CSU1980: 不堪重负的树

2017-08-13 22:12 246 查看

CSU1980: 不堪重负的树

Description

小X非常喜欢树,然后他生成了一个大森林给自己玩。
玩着玩着,小X陷入了沉思。

一棵树由N个节点组成,编号为i的节点有一个价值Wi。
假设从树根出发前往第i个节点(可能是树根自己),一共需要经过Di个节点(包括起点和终点),那么这个节点对这棵树产生的负担就是Di与Wi的乘积。
对于一棵树而言,这棵树的负担值为所有节点对它产生的负担之和。

小X学习了dfs,如果他知道树的结构,他当然可以很容易地算出树的负担值。可是现在沉思中的小X并不知道树的结构形态,他只知道一棵二叉树的中序遍历以及每个节点的价值,那么这棵二叉树可能的最小负担值是多少呢?


Input

第一行为一个正整数T(T≤20)表示数据组数。
每组数据包括三行。
第一行为一个正整数N(N≤200)。
第二行为N个正整数Wi(Wi≤108),表示编号为i的节点的价值。
第三行为N个正整数Pi(Pi≤N),为一个1~N的排列,表示二叉树的中序遍历结果。


Output

对于每组数据,输出一行一个正整数,表示这棵树可能的最小负担值。


Sample Input

2
4
1 2 3 4
1 2 3 4
7
1 1 1 1 1 1 1
4 2 3 5 7 1 6


Sample Output

18
17


Hint

对于第一个样例,树根为3,3的左儿子是2,3的右儿子是4,2的左儿子是1,这样构成的树可以达到最小负担。
对于第二个样例,对应的满二叉树可以达到最小负担。


Source

2017年8月月赛


Author

devember


这是一道区间DP题,主要是通过考虑“通过在大的区间中找到最优根节点后还需在左右子区间分别找到左右子树的最优根节点”的特征判断出了DP思路。

首先以输入中给定的中序遍历结果来考虑:记 dp[i][j] 为中序遍历结果中区间 [i,j] 对应的最小负担,那么区间 [i,j] 中必存在一个根节点 k。此时易于想象,dp[i][j] 这个最优值是来源于:

dp[i][k−1]+dp[k+1][j]+∑n=ijw[n]其中,w[n]表示n点的权值。

可以想象,这个式子从区间 [1,n] 开始,递归地执行下去,最终可以得到 dp[1][n] 的最优值。这就引导我们得出了状态转移方程:

dp[i][j]=min{dp[i][j], dp[i][k−1]+dp[k+1][j]+∑n=ijw[n]}

为简化计算,先行将权值数组处理成前缀和的形式。

于是得到可以直接套用到程序中的状态转移方程:

dp[i][j]=min{dp[i][j], dp[i][k−1]+dp[k+1][j]+wSum[j]−wSum[i−1]}

只需执行这个方程,即可通过输出 dp[1][n] 得到答案。

由于区间遍历时下标数值存在特殊情况(下界大于上界),需要确保正确地进行了初始化,从而确保取值的有效性

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
typedef long long ll;
using namespace std;
const ll maxn = 204;

ll T, n;
ll dp[maxn][maxn], iop[maxn], iow[maxn], orgw[maxn]; // iow为节点权值前缀和。dp表示的是下标范围。

int main(){
#ifdef TEST
//freopen("test.txt", "r", stdin);
#endif // TEST

cin >> T;
while(T--){
cin >> n;
memset(iow, 0, sizeof(iow));
memset(dp, 0x3f, sizeof(dp));    // 无关点的值均置为无穷大。
for(int i = 0; i <= n+1; i++)    // 确保所有下界下标大于上界下标的点的值为零(dp[x+1][x]这样的元素值需要置为零)。
for(int j = 0; j < i; j++)
dp[i][j] = 0;
for(int i = 1; i <= n; i++)
cin >> orgw[i];              // 输入初始的权值。
for(int i = 1; i <= n; i++)
cin >> iop[i];               // 输入中序遍历结果。
for(int i = 1; i <= n; i++){     // 将权值按照中序遍历结果的下标重新分配,并构造前缀和。
iow[i] = orgw[iop[i]] + iow[i-1];
dp[i][i] = iow[i] - iow[i-1];
}
for(int len = 1; len < n; len++){
for(int s = 1, e = 1+len; e <= n; s++, e++){
for(int k = s; k <= e; k++)
dp[s][e] = min(dp[s][e], dp[s][k-1] + dp[k+1][e] + iow[e] - iow[s-1]);
}
}
cout << dp[1]
<< endl;
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++ 区间dp