题目链接:https://vjudge.net/problem/POJ-2152
题意:给定一颗大小为n的树,在每个结点建消防站花费为w[i],如果某结点没有消防站,只要在它距离<=d[i]的结点有消防站即可,求最小花费。
思路:
好难的树形dp,一点思绪也木有,只能搜题解。
用dp[u][i]表示以u为根的子树满足条件,并且结点u依赖于结点i的最小花费。用best[u]表示以u根的子树满足条件的最小花费,那么best[u]=min(dp[u][i])。
求best[u]时,先跑一遍dfs得到所有结点距离u的距离dis[i]。如果dis[i]>d[u],那么u没法依赖i,此时dp[u][i]=inf。否则dis[i]<=d[u],此时dp[u][i]=w[i]+sum( min( best[v] , dp[v][i]-w[i] ) ),其中i从1遍历到n,v是u的子结点。因为v的依赖有两种情况,如果v依赖于以v为根的子树中的结点,即best[v]; 如果v依赖于其余的结点,那么一定是i。反证一下,如果v依赖于k,那么u也一定依赖于k。所以应取best[v]和dp[v][i]-w[i]的最小值,减w[i]是因为w[i]多加了一次。
AC代码:
#include<cstdio> #include<algorithm> using namespace std; const int maxn=1e3+5; const int inf=0x3f3f3f3f; int T,n,cnt,head[maxn],w[maxn],d[maxn],dp[maxn][maxn],dis[maxn]; int best[maxn]; struct node{ int v,w,nex; }edge[maxn<<1]; void adde(int u,int v,int w){ edge[++cnt].v=v; edge[cnt].w=w; edge[cnt].nex=head[u]; head[u]=cnt; } void getdis(int u,int fa,int len){ dis[u]=len; for(int i=head[u];i;i=edge[i].nex){ int v=edge[i].v; if(v==fa) continue; getdis(v,u,len+edge[i].w); } } void dfs(int u,int fa){ for(int i=head[u];i;i=edge[i].nex){ int v=edge[i].v; if(v==fa) continue; dfs(v,u); } getdis(u,0,0); best[u]=inf; for(int i=1;i<=n;++i){ if(dis[i]>d[u]) dp[u][i]=inf; else{ dp[u][i]=w[i]; for(int j=head[u];j;j=edge[j].nex){ int v=edge[j].v; if(v==fa) continue; dp[u][i]+=min(best[v],dp[v][i]-w[i]); } } best[u]=min(best[u],dp[u][i]); } } int main(){ scanf("%d",&T); while(T--){ scanf("%d",&n); cnt=0; for(int i=1;i<=n;++i) head[i]=0; for(int i=1;i<=n;++i) scanf("%d",&w[i]); for(int i=1;i<=n;++i) scanf("%d",&d[i]); for(int i=1;i<n;++i){ int u,v,w; scanf("%d%d%d",&u,&v,&w); adde(u,v,w); adde(v,u,w); } dfs(1,0); printf("%d ",best[1]); } return 0; }