POJ-2152 Fire (树形DP)
题目大意:在一棵树中选出一些点,选每个点的代价为w(i),并且对于点 i ,在距离它lim(i)之内必须选一个点,使它作为 i 的依赖点。求最小代价。
题目分析:定义状态dp(u,k)表示使u为根节点的子树满足题意并且节点u依赖节点k产生的最小代价,定义best(u)表示子树u满足题意得最小代价。则状态转移方程为:
dp(u,k)=w(k)+∑min(dp(v,k)-w(k),best(v)) dist(u,k)<=lim(u)
dp(u,k)=oo dist(u,k)>lim(u)
best(u)=min(dp(u,v))
其中v为u的子节点。
代码如下:
# include<iostream> # include<cstdio> # include<cstring> # include<vector> # include<queue> # include<list> # include<set> # include<map> # include<string> # include<cmath> # include<cstdlib> # include<algorithm> using namespace std; # define LL long long const int N=1005; const int INF=1000000000; struct Edge { int to,nxt,w; }; int cnt,n; int head[N]; int dp[N][N]; int best[N]; int dis[N][N]; int w[N],lim[N]; Edge e[N<<1]; void init() { cnt=0; memset(head,-1,sizeof(head)); for(int i=1;i<=n;++i){ best[i]=INF; for(int j=1;j<=n;++j) dp[i][j]=INF; } } void add(int u,int v,int w) { e[cnt].to=v; e[cnt].w=w; e[cnt].nxt=head[u]; head[u]=cnt++; } void read() { scanf("%d",&n); init(); for(int i=1;i<=n;++i) scanf("%d",w+i); for(int i=1;i<=n;++i) scanf("%d",lim+i); int a,b,c; for(int i=1;i<n;++i){ scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,c); } } void getDis(int s,int u,int fa,int d) { dis[s][u]=d; for(int i=head[u];i!=-1;i=e[i].nxt){ int v=e[i].to; if(v==fa) continue; getDis(s,v,u,d+e[i].w); } } void dfs(int u,int fa) { for(int i=head[u];i!=-1;i=e[i].nxt){ int v=e[i].to; if(v==fa) continue; dfs(v,u); } for(int i=1;i<=n;++i){ if(lim[u]<dis[u][i]) continue; dp[u][i]=w[i]; for(int j=head[u];j!=-1;j=e[j].nxt){ int v=e[j].to; if(v==fa) continue; dp[u][i]+=min(dp[v][i]-w[i],best[v]); } best[u]=min(best[u],dp[u][i]); } } void solve() { for(int i=1;i<=n;++i) getDis(i,i,-1,0); ///找出任意两点之间的距离; dfs(1,-1); printf("%d\n",best[1]); } int main() { int T; scanf("%d",&T); while(T--) { read(); solve(); } return 0; }