题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6201
题意:给出一棵树,每个点有一个权值,代表商品的售价,树上每一条边上也有一个权值,代表从这条边经过所需要的花费。现在需要你在树上选择两个点,一个作为买入商品的点,一个作为卖出商品的点,当然需要考虑从买入点到卖出点经过边的花费。使得收益最大。允许买入点和卖出点重合,即收益最小值为0。
解法:我们设1为根节点,假设一开始一个人身上的钱为0。我们设dp[i][0]表示从根节点走到i及其子树并中任一点买入一本书后这个人身上钱的最大值(显然是负的)。dp[i][1]表示从根节点走到i及其子树并中任一点卖出一本书后这个人身上钱的最大值(可正可负)。那么我们对这棵树进行一次树形DP即可,dfs后对每个节点更新收益最大值,单点的计算方法为dp[i][0]+dp[i][1],树形DP的过程中即可维护这个最大值。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 100010;
struct node{
int v,w;
};
vector <node> G[maxn];
int val[maxn];
int dp[maxn][2];
int n, ans;
void dfs(int x, int pre){
dp[x][0] = -val[x];
dp[x][1] = val[x];
for(int i=0; i<G[x].size(); i++){
int v = G[x][i].v;
int w = G[x][i].w;
if(v == pre) continue;
dfs(v, x);
dp[x][0] = max(dp[x][0], dp[v][0]-w);
dp[x][1] = max(dp[x][1], dp[v][1]-w);
}
ans = max(ans, dp[x][0]+dp[x][1]);
}
int main()
{
int T;
scanf("%d", &T);
while(T--){
scanf("%d", &n);
for(int i=0; i<=n; i++) G[i].clear();
for(int i=1; i<=n; i++) scanf("%d", &val[i]);
for(int i=1; i<n; i++){
int u, v, w;
scanf("%d %d %d", &u,&v,&w);
G[u].push_back(node{v,w});
G[v].push_back(node{u,w});
}
ans = 0;
dfs(1,-1);
printf("%d\n", ans);
}
return 0;
}
除了DP,还看到一个方法,就是建立源点和汇点。源点连所有的树上点, 边权为 a[i], 所有树上点在连接 汇点, 边权为-a[i]. 然后在根据树建图。 spfa跑个最长路即可。这个也可以用费用流,不过要注意是可行流。