Computer HDU - 2196 (树形dp || 树的直径)
题目链接
题目大意:
给定一棵树,知道相邻结点的距离,问对于每一个结点,和距离它最远的结点之间的距离是多少。
Input:
5 n个结点
1 1 表示2和1之间的距离是1
2 1 表示3和2之间的距离是1
3 1 表示4和3之间的距离是1
1 1 表示5和1之间的距离是1
Output
3
2
3
4
4
题目思路:
有两种做法,一种是树的直径,一种是树形dp。先讲下树形dp。
dp[i][0] 从root节点更新过来的 这里的路一定和dp[i][1]不一样
dp[i][1] 子树的最大距离
dp[i][2] 子树的次大距离
1.很显然,对于一个结点,它的最长的距离有两种:一是它向下走,走到它子树的叶子结点,即dp[i][1]。第二种是从它的根过来的一条路,即dp[i][0]。因为这个结点的出路只有这两种,这样我们就很容易想到最终答案是取这两种的最大值了。
2.第一种我们发现很好求,就是一个dfs求深度的过程。
3.第二种我们发现可以从它的根节点推过来。比如说根是root,它的孩子有u1, u2, u3。这时我们要求dp[u1][0],root的路也是有两条, 一条是从root的父亲过来的路dp[root][0],一条是root的子树dp[root][1]。而dp[u1][0]是root的两条路的最大值。我们发现root的子树是包含u1的,所以dp[root][1]不一定对,因为dp[root][1] + w可能会算多,所以我们要分dp[root][1]是不是包含u1两种情况。
就是说 当dp[root][1]不是包含u1时:dp[u1][0] = max(dp[root][1], dp[root][0]) + w
当dp[root][1]是包含u1时: dp[u1][0] = max(dp[root][2], dp[root][0]) + w
4.强调一下,这里的dp[root][2]虽然是root子树的次大值,但是这条路一定不会经过u1,为什么呢。
我们看下面的代码可以看出,更新root的最大次大值的时候,是直接比较的u1,u2,u3的大小,所以这里的次小值一定是可行的。
5.最后一点,怎么知道dp[root][1]包不包含u1,这也是我想了很久的一个问题。后来看了题解,如果说dp[root][1]包含u1的话, dp[u1][1] + dist(root, u1) 是等于 dp[root][1]的
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 100;
int data[maxn], dp[maxn][3];
bool vis[maxn];
//dp[i][0] 从root节点更新过来的
//dp[i][1] 子树的最大距离
//dp[i][2] 子树的次大距离
struct node
{
int v, w;
};
vector<node> Edge[maxn];
int cnt = 0;
void dfs1(int root, int pre) {
int biggest = 0, bigger = 0;
for(int i = 0; i < Edge[root].size(); i++) {
int to = Edge[root][i].v, w = Edge[root][i].w;
if(to == pre) continue;
dfs1(to, root);
if(dp[to][1] + w >= biggest) {
bigger = biggest;
biggest = dp[to][1] + w;
}
else if(dp[to][1] + w > bigger) {
bigger = dp[to][1] + w;
}
}
dp[root][1] = biggest;
dp[root][2] = bigger;
}
void dfs2(int root, int pre) {
for(int i = 0; i < Edge[root].size(); i++) {
int to = Edge[root][i].v, w = Edge[root][i].w;
if(to == pre) continue;
dp[to][0] = max(dp[root][0], dp[to][1] + w == dp[root][1] ? dp[root][2]: dp[root][1]) + w;
dfs2(to, root);
}
}
int main()
{
int n;
while(~scanf("%d", &n))
{
for(int i = 0; i <= n; i++) Edge[i].clear();
for(int i = 2; i <= n; i++)
{
int v, w;
scanf("%d %d", &v, &w);
Edge[i].push_back((node) {v, w});
Edge[v].push_back((node) {i, w});
}
dfs1(1, 0); //跑出最长的距离和次长的距离
dfs2(1, 0); //跑出dp[i][0]
for(int i = 1; i <= n; i++)
printf("%d\n", max(dp[i][1], dp[i][0]));
}
}
第二种做法 树的直径
树的直径的定义:树上最长的简单路径
求树的直径的方法:在树上任选一点u,求距离点u最远的点y,再求距离点y最远的点s,点y到点s的距离即为树的直径。
因为u的最远点y一定是这棵树的一个端点。
对于这个题,图中任意一个点到这两个节点的距离其中必有一个距离是这个点到其它点的距离的最大值,即距离要找的点最远的点一定出现在树的直径的两个端点之一。
然后我们去分别跑这两个端点到所有点的距离,用数组存起来,然后取最大值就可以了。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 100;
int data[maxn], dp[maxn], dist1[maxn], dist2[maxn];
bool vis[maxn];
struct node
{
int v, w;
};
vector<node> Edge[maxn];
int bfs(int root, int dist[])
{
memset(vis, 0, sizeof(vis));
queue<node> q;
q.push((node) {root, 0});
int Max = -1, pos;
vis[root] = true;
dist[root] = 0;
while(!q.empty())
{
node now = q.front();
q.pop();
dist[now.v] = now.w;
if(now.w > Max)
{
Max = now.w;
pos = now.v;
}
for(int i = 0; i < Edge[now.v].size(); i++)
{
int to = Edge[now.v][i].v, w = now.w;
if(!vis[to])
{
q.push((node) {to, w + Edge[now.v][i].w});
vis[to] = true;
}
}
}
return pos;
}
int main()
{
int n;
while(~scanf("%d", &n))
{
for(int i = 0; i <= n; i++) Edge[i].clear();
for(int i = 2; i <= n; i++)
{
int v, w;
scanf("%d %d", &v, &w);
Edge[i].push_back((node) {v, w});
Edge[v].push_back((node) {i, w});
}
int p1 = bfs(1, dist1);
int p2 = bfs(p1, dist1);
bfs(p1, dist1);
bfs(p2, dist2);
for(int i = 1; i <= n; i++)
{
printf("%d\n", max(dist1[i], dist2[i]));
}
}
}