题目大意:给定一棵有根树,1 号节点为根节点,点有点权,边有边权,初始给定一个价值,每经过一条边都会减少该价值,每经过一个点都会增加相应的答案贡献值,求如何在给定价值的情况下最大化答案贡献,并要求最后在 N 号节点停留,若无法停留,则输出相应字符串。
题解:
首先,不考虑要求在 N 号节点停留的限制,发现就是一个裸的树上背包问题。但是现在多了一个限制条件,我们假设一定可以从根节点走到 N 号节点。
引理:最有情况下,从根节点到 N 号节点的树链中的每一条边经过且仅经过一次。
证明:若先经过树链上的边 e ,再通过 e 返回,再经过其他非树链边之后,再次返回 e,得到的一条路径 P。构造一条路径 P‘,使得在未经过 e 时,先经过 P’ 中的对应非树边,再经过 e ,得到路径 P。发现 P 总是比 P‘ 小 2e 的代价,证毕。
根据引理,又根据从 1 到 N 的路径必须经过的性质,采用将这条树链压缩成一个点,即:将这条路径上的点压缩成一个点,再在新的树上进行树形dp操作,得到的最优解即是全局最优解。对于压缩路径的等价操作是:记录这条必经树链的每条边,将边权全部置为 0 即可。
代码如下
#include <bits/stdc++.h>
using namespace std;
const int maxn=105;
const int maxt=505;
int n,t,a[maxn];
struct node{int nxt,to,w;}e[maxn<<1];
int tot=1,head[maxn];
inline void add_edge(int from,int to,int w){
e[++tot]=node{head[from],to,w},head[from]=tot;
}
int d[maxn],pre[maxn],f[maxn][maxt];
void dfs(int u,int fa){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to,w=e[i].w*2;
if(v==fa)continue;
dfs(v,u);
for(int j=t;j>=0;j--)
for(int k=0;k<=j-w;k++)
f[u][j]=max(f[u][j],f[u][j-k-w]+f[v][k]);
}
for(int j=0;j<=t;j++)f[u][j]+=a[u];
}
void getdis(int u,int fa){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to,w=e[i].w;
if(v==fa)continue;
pre[v]=i,d[v]=d[u]+w;
getdis(v,u);
}
}
void clear(){
int now=n;
while(now!=1){
int id=pre[now];
e[id].w=e[id^1].w=0;
now=e[id^1].to;
}
}
void read_and_parse(){
for(int i=1;i<n;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add_edge(x,y,z),add_edge(y,x,z);
}
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
}
void solve(){
getdis(1,0);
if(d[n]>t)return (void)puts("Human beings die in pursuit of wealth, and birds die in pursuit of food!");
clear();
t-=d[n];
dfs(1,0);
printf("%d
",f[1][t]);
}
void init(){
memset(head,0,sizeof(head)),tot=1;
memset(pre,0,sizeof(pre));
memset(d,0,sizeof(d));
memset(f,0,sizeof(f));
}
int main(){
while(scanf("%d%d",&n,&t)!=EOF){
init();
read_and_parse();
solve();
}
return 0;
}