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]);
}
}
}