1. 程式人生 > 實用技巧 >《演算法競賽進階指南》0x54樹形DP POJ3585積蓄程度

《演算法競賽進階指南》0x54樹形DP POJ3585積蓄程度

題目連結:http://poj.org/problem?id=3585

給定一棵樹,邊上面代表的是流量,可以選定一個點作為源點,一個點作為匯點,匯點的出量無限,源點的入量無限,問選擇哪一個點能使得樹中這個點為源點的流量最大。

採用的演算法是二次掃描與換根法,第一次掃描的時候選擇一個root自底向上算出d的值,也就是從這個點出發向下的最大流量,一次掃描後root結點的流量是確定的,複製到f[root]中,接下來對自頂向下更新子樹的f陣列的值。更新的時候用上已經是真實f的父節點的f陣列值以及當前點的d陣列的值。注意在父節點和子節點是葉子節點的時候要單獨計算f函式的值,因為實際上葉子結點計算的d陣列值是0,但是實際上應該是inf,所以在d陣列是非真實的時候要單獨計算。

程式碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 200010;
const int inf=0x3f3f3f3f;
int n;
int head[maxn],nxt[maxn<<1],len[maxn<<1],ver[maxn<<1];
int f[maxn],d[maxn];
int deg[maxn];
int cnt=0;
void
addedge(int a,int b,int c){ ver[++cnt]=b; len[cnt]=c; nxt[cnt]=head[a]; head[a]=cnt; } void dfs_d(int u,int fa){ d[u]=0; for(int i=head[u];i;i=nxt[i]){ int v=ver[i]; int w=len[i]; if(v==fa)continue; dfs_d(v,u); if(deg[v]==1)d[u]+=w;
else d[u]+=min(d[v],w); } } void dfs_f(int u,int fa){ //u及以上的部分f陣列已經計算完畢, //通過父節點更新子節點的資訊 for(int i=head[u];i;i=nxt[i]){ int v=ver[i]; int w=len[i]; if(v==fa)continue; if(deg[u]==1)f[v]=d[v]+w;//父節點是葉子結點 else if(deg[v]==1)f[v]=min(f[u]-w,w); else f[v]=d[v]+min(f[u]-min(d[v],w),w); dfs_f(v,u);//計算以v為根節點的子樹的所有f } } int main(){ int T; scanf("%d",&T); while(T--){ cnt=0; scanf("%d",&n); memset(head,0,sizeof(head)); memset(deg,0,sizeof deg); for(int i=0;i<n-1;i++){ int a,b,c; scanf("%d%d%d",&a,&b,&c); addedge(a,b,c); addedge(b,a,c); deg[a]++,deg[b]++; } memset(d,0,sizeof(d)); dfs_d(1,-1);//算出d陣列 f[1]=d[1]; dfs_f(1,-1); int res=0; for(int i=1;i<=n;i++)res=max(res,f[i]); printf("%d\n",res); } return 0; }