CSU1980: 不堪重负的树

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][k1]+dp[k+1][j]+n=ijw[n]w[n]n

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

为简化计算,先行将权值数组处理成前缀和的形式。
于是得到可以直接套用到程序中的状态转移方程:
dp[i][j]=min{dp[i][j],  dp[i][k1]+dp[k+1][j]+wSum[j]wSum[i1]}

只需执行这个方程,即可通过输出 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][n] << endl;
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值