Magic boy Bi Luo with his excited tree HDU - 5834

HDU - 5834 Magic boy Bi Luo with his excited tree

题意: 每个点和边都有个权值, 通过这条边会消费权值, 通过这个点会得到权值, 一个点只能得到一次权值,但是边访问多次会消费多次权值。 可以重复经过一个点多次,问每个点可以得到的最大权值是多少?
题解:

​ 我认为这题是个比较难的题,我想了好久没想出来,最后参考别人的题解才知道如何去写。

​ 下面我就详细说下这题的解法。

设 $ dp[u][0] $ 表示 从u节点为起点, 走向u的子树中不回来(即终点不是u)得到的最大值。

$ dp[u][1] $ 表示从u节点为起点, 走向u的子树中不回来的次大值。

$ dp[u][2] $ 表示从u节点为起点, 走向u的子树中回来的最大值(即终点是u).

在这里的最大值与次大值值的是 从u开始走的第一步(也就是儿子节点),不同儿子的最大与次大。

用 $ id[u] $ 表示 最大值(不回来的)第一步走的儿子节点。

如何求出这些值?

看代码:

// a[u]表示节点为u的权值.
void dfs(int u, int fa){
    dp[u][0] = dp[u][1] = dp[u][2] = a[u];
    // 求回来的最大收益
    for(int i = head[u]; i; i = e[i].nxt){      // 建议用链式前向星存图
        int to = e[i].to;
        int cost = e[i].cost;
        if(to == fa)continue;
        dfs(to, u);
        if(dp[to][2] + 2 * cost > 0){         // 存的就是负边所以直接加
            dp[u][2] += dp[to][2] + 2 * cost;   // 由于 dp[u][2]求的是回来的最大值, 当回来收益大于0时我贪心加在一起
        }
    }
    id[u] = u;
    // 求不回来的最大与次大收益
    for(int i = head[u]; i; i = e[i].nxt){
        int to = e[i].to;
        int cost = e[i].cost;
        if(to == fa) continue;
        int now = dp[u][2] - max(0, dp[to][2] + 2 * cost) + max(0, dp[to][0] + cost);
        if(dp[u][0] < now ){
            dp[u][1] = dp[u][0];        // 计算次大值
            dp[u][0] = now ;  // 计算最大值
            id[u] = to;                     // 最大值第一步走的儿子节点
        }else if(dp[u][1] < now ){
            dp[u][1] = now ;      // 更新次大值
        }
    }
}

有了每个节点到子树不回来的最大值与次大值,和回来的最大值。

下一步变是便每个节点经过父亲节点回来的最大值, 经过父亲节点不回来的最大值。

如何去求呢?

这是我们再来一次dfs但是这次dfs加了两个参数noback, back分别表示 当前节点经过父亲节点不回来的最大值, 与经过父亲节点在回到父亲节点的最大值。

如何求每个节点对应的 noback,back 的值?

具体看代码:

void dfs1(int u, int fa, int noback, int back){
    ans[u] = max(dp[u][0] + back, dp[u][2] + noback); // 只会有从父亲回来走儿子或者从儿子回来走父亲, 其它情况都不是最优的。

    for(int i = head[u]; i; i = e[i].nxt){
        int to = e[i].to;
        int cost = e[i].cost;
        if(fa == to) continue;
        int now_no_back, now_back; // 分别表示父亲节点 不回来/回来且不经过to节点的最大值
        if(to == id[u]){ // 如果成立,表示当前节点包含了最大值如果选最大值会重复计算, 所以选次大值
            now_no_back = dp[u][1] - max(0, dp[to][2] + 2 * cost);
        }else{
            now_no_back = dp[u][0] - max(0, dp[to][2] + 2 * cost);
        }
        now_back = dp[u][2] - max(0, dp[to][2] + 2 * cost);
        now_no_back = max(now_no_back, back + now_no_back) + cost;
        now_no_back = max(now_no_back, now_back + noback + cost);
        now_back = max(now_back + 2 * cost, now_back + back + 2 * cost);
        dfs1(to, u, max(0, now_no_back), max(0, now_back));
    


    }
}

ac代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
int t, a[N], head[N], top = 1, n;
int dp[N][3], id[N], ans[N];

struct edge{
    int to, nxt, cost;
}e[2 * N];

void add_edge(int u, int v, int w){
    e[top].to = v;
    e[top].cost = w;
    e[top].nxt = head[u];
    head[u] = top++;
}


// a[u]表示节点为u的权值.
void dfs(int u, int fa){
    dp[u][0] = dp[u][1] = dp[u][2] = a[u];
    // 求回来的最大收益
    for(int i = head[u]; i; i = e[i].nxt){      // 建议用链式前向星存图
        int to = e[i].to;
        int cost = e[i].cost;
        if(to == fa)continue;
        dfs(to, u);
        if(dp[to][2] + 2 * cost > 0){         // 存的就是负边所以直接加
            dp[u][2] += dp[to][2] + 2 * cost;   // 由于 dp[u][2]求的是回来的最大值, 当回来收益大于0时我贪心加在一起
        }
    }
    id[u] = u;
    // 求不回来的最大与次大收益
    for(int i = head[u]; i; i = e[i].nxt){
        int to = e[i].to;
        int cost = e[i].cost;
        if(to == fa) continue;
        int now = dp[u][2] - max(0, dp[to][2] + 2 * cost) + max(0, dp[to][0] + cost);
        if(dp[u][0] < now ){
            dp[u][1] = dp[u][0];        // 计算次大值
            dp[u][0] = now ;  // 计算最大值
            id[u] = to;                     // 最大值第一步走的儿子节点
        }else if(dp[u][1] < now ){
            dp[u][1] = now ;      // 更新次大值
        }
    }
}

void dfs1(int u, int fa, int noback, int back){
    ans[u] = max(dp[u][0] + back, dp[u][2] + noback); // 只会有从父亲回来走儿子或者从儿子回来走父亲, 其它情况都不是最优的。

    for(int i = head[u]; i; i = e[i].nxt){
        int to = e[i].to;
        int cost = e[i].cost;
        if(fa == to) continue;
        int now_no_back, now_back; // 分别表示父亲节点 不回来/回来且不经过to节点的最大值
        if(to == id[u]){ // 如果成立,表示当前节点包含了最大值如果选最大值会重复计算, 所以选次大值
            now_no_back = dp[u][1] - max(0, dp[to][2] + 2 * cost);
        }else{
            now_no_back = dp[u][0] - max(0, dp[to][2] + 2 * cost);
        }
        now_back = dp[u][2] - max(0, dp[to][2] + 2 * cost);
        now_no_back = max(now_no_back, back + now_no_back) + cost;
        now_no_back = max(now_no_back, now_back + noback + cost);
        now_back = max(now_back + 2 * cost, now_back + back + 2 * cost);
        dfs1(to, u, max(0, now_no_back), max(0, now_back));
    


    }
}


int main(){
    scanf("%d", &t);
    int ca = 1;
    while(t--){
        top = 1;
        scanf("%d", &n);
        for(int i = 0; i <= n + 1; i++){
            head[i] = 0;
            dp[i][0] = dp[i][1] = dp[i][2] = 0;
        }
        for(int i = 1; i <= n; i++){
            scanf("%d", &a[i]);
        }
        for(int i = 1; i < n; i++){
            int u, v, w;
            scanf("%d %d %d", &u, &v, &w);
            add_edge(u, v, -w);
            add_edge(v, u, -w);
        }
        dfs(1, 0);
        dfs1(1, 0, 0, 0);
        printf("Case #%d:\n", ca++);
        for(int i = 1; i <= n; i++){
            printf("%d\n", ans[i]);
        }
        
    }
}
posted @ 2020-06-25 17:08  ccsu_zhaobo  阅读(66)  评论(0编辑  收藏  举报