CPPC网络选拔赛 C题 [HDU 5834] Magic boy Bi Luo with his excited tree (树形dp)

链接

HDU 5834


题意

给出N个节点的树,每个节点含权,且点权值只可获得一次,每条边也含权,但每次通过边时都会减少相应边权的价值。对每个节点求出,从该节点出发能获得的最大总价值。


思路

模型是一道很经典的树形dp:HDU 2196 对所有节点都求出最值。

本题为该模型的复杂化版本,数据规模大,需要在线性级别的树形dp内取得对所有点的最值。
仍然是一次dfs下去,对多有节点取得“ 子关系”上的局部最优解,本题性质需要记录回到节点的最大价值和不回到节点的最大价值和次大价值。我们使用d_back[i]和d_down[i][0/1]表示,注意对根节点而言,这些值已经是它的全局最优解,因为其没有“父关系”上的贡献。
假设d_back[u]和d_down[u][0/1]已是节点u的全局最优解,check[u]是取得d_down[u][0]时选择不回本节点的边,v是u的一个子节点,那么对v而言,它的ans是下列值之一:
d_back[v] + g(d_down[u][0]) (check[u] != v)
d_back[v] + g(d_down[u][1]) (check[u] == v)
d_down[v][0] + g(d_back[u])
g()意味着“父关系”上的贡献,这要减去v部分对u提供的贡献。
注意在取得v的ans以后,需要对v的d_back和d_down进行更新,使其成为全局最优解,之后才可向下递归利用v的全局最优解解决其子节点的问题。


总结

这题一共用到两个模型,期中一个是树中常见的从某点出发去旅行的最大收益,关于这类问题一般dp方程中需要出现从某点出发“回到”出发点的最大收益和从某点出发“不回到”出发点的最大收益,显然后者是最终解。这么做的原因是要进行dp,从某点出发的最优解最多只能选一条临边“不回到”出发点,其他临边出发都要回到原点。

另一个模型就是那个经典的对所有点都取最优解的模型,将“子向”最优解和“父向”最优解拼凑成全局最优解。注意拼凑以后要更新该点的最优解才可继续向下dp,由于根节点已经是全局最优解,所以dp可以进行下去。


代码
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
#define maxn (100010)
vector<int> son[maxn], cost[maxn];
int d_down[maxn][2], d_back[maxn], edge_id[maxn], vis[maxn], w[maxn];
void dp1(int u)
{
    vis[u] = 1;
    d_back[u] = w[u];
    for(int i = 0, v, c; i < son[u].size(); i++)
    if(!vis[v = son[u][i]]) { c = cost[u][i];
        dp1(v);
        d_back[u] += max(0, d_back[v] - c - c);
    }
    d_down[u][0] = d_down[u][1] = d_back[u];
    edge_id[u] = 0;
    for(int i = 0, v, c, d; i < son[u].size(); i++)
    if(!vis[v = son[u][i]]) { c = cost[u][i];
        d = d_back[u] - max(0, d_back[v] - c - c) + max(0, d_down[v][0] - c);
        if(d > d_down[u][0]) { d_down[u][1] = d_down[u][0]; d_down[u][0] = d; edge_id[u] = v; }
        else if(d > d_down[u][1]) d_down[u][1] = d;
    }
    vis[u] = 0;
}
void dp2(int u)
{
    //printf("##### u = %d\n", u);
    vis[u] = 1;
    for(int i = 0, v, c, di, dj; i < son[u].size(); i++)
    if(!vis[v = son[u][i]]) { c = cost[u][i];
        di = d_back[u] - max(0, d_back[v] - c - c);
        if(edge_id[u] == v) dj = d_down[u][1] - max(0, d_back[v] - c - c);
        else dj = d_down[u][0] - max(0, d_back[v] - c - c);

        //printf("### v = %d\n", v);


        dj = d_back[v] + (dj - c > 0) * (dj - c);
        di = d_down[v][0] + (di - c - c > 0) * (di - c - c);

        //printf("# di = %d, dj = %d\n", di, dj);

        if(dj > di) { edge_id[v] = 0; d_down[v][0] = dj; d_down[v][1] = di; }
        else if(dj > di - d_down[v][0] + d_down[v][1]){ d_down[v][0] = di; d_down[v][1] = dj; }
        else { d_down[v][1] = di - d_down[v][0] + d_down[v][1]; d_down[v][0] = di; }
        di = d_back[u] - max(0, d_back[v] - c - c);
        d_back[v] += max(0, di - c - c);

        dp2(v);
    }
    vis[u] = 0;
}
int main()
{
    int T, kase = 0;
    cin >> T;
    while(T--)
    {
        int n;
        cin >> n;
        for(int i = 1; i <= n; i++)
        {
            scanf("%d", &w[i]);
            son[i].clear();
            cost[i].clear();
        }
        for(int i = 0, u, v, c; i < n-1; i++)
        {
            scanf("%d%d%d", &u, &v, &c);
            son[u].push_back(v);
            cost[u].push_back(c);
            son[v].push_back(u);
            cost[v].push_back(c);
        }
        dp1(1); //
        /*for(int i = 1; i <= n; i++)
        {
            printf("#%d:", i);
            printf(" %d %d\n", d_back[i], d_down[i][0]);
        }*/
        dp2(1);
        printf("Case #%d:\n", ++kase);
        for(int i = 1; i <= n; i++)
            printf("%d\n", d_down[i][0]);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值