POJ 1849 - Two - [DFS][树形DP]
Time Limit: 1000MS Memory Limit: 30000K
Description
Heavy snow covered the city so the mayor Milan gave to the winter-service a list of streets that have to be cleaned of snow. These streets are chosen such that the number of streets is as small as possible but still every two intersections to be connected i.e. between every two intersections there will be exactly one path. The winter service consists of two snow plovers and two drivers, Mirko and Slavko, and their starting position is on one of the intersections.
The snow plover burns one liter of fuel per meter (even if it is driving through a street that has already been cleared of snow) and it has to clean all streets from the list in such order so the total fuel spent is minimal. When all the streets are cleared of snow, the snow plovers are parked on the last intersection they visited. Mirko and Slavko don’t have to finish their plowing on the same intersection.
Write a program that calculates the total amount of fuel that the snow plovers will spend.
Input
Each of the next N-1 lines contains three integers: A, B and C, meaning that intersections A and B are directly connected by a street and that street's length is C meters, 1 <= C <= 1000.
Output
Sample Input
5 2 1 2 1 2 3 2 3 4 2 4 5 1
Sample Output
6
题意:
这个城市由节点和连接节点的街道组成,街道是双向的。
此刻大雪覆盖了这个城市,市长确定了一些街道要将它们清扫干净,这些街道保证所有的节点可以通过它们连通而且街道数目尽可能小。
现有两台相同的扫雪机S和M,它们的起点在同一个节点上。
所有被确定的街道必须至少被一台扫雪机经过,才能完成清扫任务,完成任务后S和M可以在原地停下,不必集合到某一点。
扫雪机的行进是需要耗费油量的(即使扫雪机行驶的是已被扫净的街道),因此扫雪机行进的总距离越小越好,你需要计算两台扫雪机完成任务的最小总行进距离。
题解:
我们知道,树都有一条直径,那么,
假设从这棵树上的某一点出发,遍历完所有的点之后回到该点需要走的边数是个固定值,为这棵子树的边数的两倍;
具体为什么,不带方向的树,任意一点都可作为树根,那么把选到的那一点作为树根,进行DFS模拟,很容易看出遍历完所有的点需要两倍边长总和;
但是根据我们题目的要求,其实遍历到最后剩下的一个点后,是没有必要返回的;
那么答案应该为边数的两倍减去始末这两点之间的距离,要使结果最优,必然要使这两点之间的距离尽可能的大,而树上两点之间的距离的最大值便为树的直径;
也就是说,如何寻找一条最短的路径遍历完一整棵树,应该以树的直径一端为起点,另一端为终点进行遍历。
回到本题,两辆车分担工作,那么就可以将两辆车的终点分别设为树直径的两个端点,而行进路线同上一模一样,那么这两辆车的起点在哪儿就无关紧要了:
①如果我们的起始点在直径上,那么很显然的,两辆车分别朝两个方向走,遇到直径上的分叉,就要花费分叉的总长*2的路程来遍历这些分叉,而直径上的边都只需要走一遍即可
②而如果我们的起始点不在直径上,那么他们两辆车只需要先把这个分叉遍历一遍,最后回到直径上,那么又可以按①的过程,两辆车朝两边走。
综上,我们就可以知道,这样需要走过的总路程 ans = "直径长度" + 2 * "所有分叉上边的长度总和",换句话说,就是ans = 2 * "所有边的长度总和" - "直径长度";
至于,求树的直径,我们可以有两种方法:
参考:http://blog.csdn.net/tc_to_top/article/details/47002255
首先是两次DFS的过程:
1 #include<cstdio> 2 #include<vector> 3 #define MAXN 100000+5 4 using namespace std; 5 struct Edge{ 6 int u,v,w; 7 }; 8 vector<Edge> adj[MAXN]; 9 int n,s,d[MAXN],sum; 10 void dfs(int now,int par) 11 { 12 for(int i=0;i<adj[now].size();i++) 13 { 14 Edge edge=adj[now][i]; 15 int next=edge.v; 16 if(next==par) continue; 17 d[next]=d[now]+edge.w; 18 dfs(next,now); 19 } 20 } 21 int main() 22 { 23 while(scanf("%d%d",&n,&s)!=EOF) 24 { 25 sum=0; 26 for(int i=1;i<=n;i++) adj[i].clear(); 27 for(int i=1;i<n;i++) 28 { 29 int u,v,w; 30 scanf("%d%d%d",&u,&v,&w); 31 sum+=w; 32 adj[u].push_back((Edge){u,v,w}); 33 adj[v].push_back((Edge){v,u,w}); 34 } 35 d[s]=0; 36 dfs(s,-1); 37 int max=0,max_i; 38 for(int i=1;i<=n;i++) 39 { 40 if(max<d[i]) 41 { 42 max=d[i]; 43 max_i=i; 44 } 45 } 46 d[max_i]=0; 47 dfs(max_i,-1); 48 int diameter=0; 49 for(int i=1;i<=n;i++) if(diameter<d[i]) diameter=d[i]; 50 printf("%d\n",2*sum-diameter); 51 } 52 }
至于树形DP的方法,思维其实和两次DFS异曲同工,通过DFS可以得到任何一个点的dp[i][0]和dp[i][1]:
dp[i][0]存储点i延伸出去所能达到的最远长度,dp[i][1]存储点i从另一完全不同的路线(意思是一条重合的边都不能有)延伸出去所能到达的最远长度(称其为次远长度);
那么显然,所有点中,只有在树直径上的那些个点的dp[i][0]+dp[i][1]是最大的,而此时dp[i][0]+dp[i][1]的值即为树的直径长度。
1 #include<cstdio> 2 #include<cstring> 3 #include<vector> 4 #define MAXN 100000+5 5 using namespace std; 6 struct Edge{ 7 int u,v,w; 8 }; 9 vector<Edge> adj[MAXN]; 10 int n,s,sum,diameter; 11 int dp[MAXN][2]; 12 void dfs(int now,int par) 13 { 14 for(int i=0;i<adj[now].size();i++) 15 { 16 Edge edge=adj[now][i]; 17 int next=edge.v; 18 if(next==par) continue; 19 dfs(next,now); 20 if(dp[now][0] < dp[next][0]+edge.w) // ( "其某个孩子的最大"+"其与孩子的距离" ) > "最大" > "次大" 21 { 22 dp[now][1] = dp[now][0]; 23 dp[now][0] = dp[next][0] + edge.w; 24 } 25 else if(dp[now][1] < dp[next][0]+edge.w) // "最大" > ( "其某个孩子的最大"+"其与孩子的距离" ) > "次大" 26 { 27 dp[now][1] = dp[next][0]+edge.w; 28 } 29 } 30 if(diameter<dp[now][0]+dp[now][1]) diameter=dp[now][0]+dp[now][1]; 31 } 32 int main() 33 { 34 while(scanf("%d%d",&n,&s)!=EOF) 35 { 36 diameter=sum=0; 37 for(int i=1;i<=n;i++) adj[i].clear(); 38 memset(dp,0,sizeof(dp)); 39 for(int i=1;i<n;i++) 40 { 41 int u,v,w; 42 scanf("%d%d%d",&u,&v,&w); 43 sum+=w; 44 adj[u].push_back((Edge){u,v,w}); 45 adj[v].push_back((Edge){v,u,w}); 46 } 47 dfs(s,-1); 48 printf("%d\n",2*sum-diameter); 49 } 50 }