这是一个有趣的题目,我们来详细分析题目的解法
描述
H 国有 n 座城市和 n-1 条无向道路,保证每两座城市都可以通过道路互相到达。现在 H 国要开始施工,施工分若干个阶段,第 i 个阶段会建设无向道路 (x,y)
,当且仅当存在一个数 z,满足 x ≠ z, x ≠ y, z ≠ y,且在第 i-1 个阶段后,存在无向道路 (x,z), (z,y).
现在 H 国的国王想知道,在几个阶段后,每两个不同的城市之间都有一条无向道路.
输入
第一行一个正整数 n
接下来 n-1 行,每行两个正整数 (x,y),描述一开始的一条无向道路 (x,y)
1 ≤ n ≤ 105
输出
输出最少几个阶段后,每两个不同的城市之间都有一条无向道路.
样例输入:
3
1 2
2 3
样例输出:
1
从题目的含义上看,就是找出最少的步数,每一步,将树中两点间最短距离为2的点(每条边的长度看做1),添加一条边将两点的最短距离变为1.很明显通过一步一步的操作,最终所有两点间的距离都会变成1,也就是国王希望达到的效果。
最初分析这个题目可能还无法找到合适的切入点,但是你换一个角度来看问题: 国王希望的效果,就是希望图的任意两点间的最短距离为1,而且通过一步一步的操作,原来的点之间的距离是一定会缩短的,每做一步操作,两个点之间的距离都会缩小一点,那我们可以先找到树中距离最大的两个点,然后看多少步操作能将这两个点的距离减为1,这就是我们需要求解的最小操作步数。而且每做一步操作,两点之间的距离会缩短一半(奇数距离和偶数距离有一点区别,需要注意)。
所以上述问题的求解方案的关键步骤就转化成了,求解树中的任意两点间的最大距离。其实这个问题就是求树的直径,关于如何求树的直径后面的文章再详细介绍。接下来是代码实现。
import java.util.*;
class TreeNode {
public int node_id = -1; //树节点的ID
public ArrayList<TreeNode> friends = new ArrayList<TreeNode>(); //兄弟节点
public int dist = -1; //用于后文中求解最大距离
};
/**
* 求解离树tree_node_id距离最远的点
*/
public int getMaxDistTreeNode(TreeNode[] tree_node_list, int tree_node_id) {
for(int i = 1; i < tree_node_list.length; i++) {
tree_node_list[i].dist = -1; // 代表没有被操作过
}
//计算每个树节点到tree_node_id节点的距离
tree_node_list[tree_node_id].dist = 0;
LinkedList<Integer> handle_list = new LinkedList<Integer>();
handle_list.add(tree_node_id);
while(handle_list.size() != 0) {
int handle_tree_id = handle_list.pollFirst();
TreeNode handle_node = tree_node_list[handle_tree_id];
for(TreeNode friend_node : handle_node.friends) {
if(friend_node.dist != -1) continue;
friend_node.dist = handle_node.dist + 1;
handle_list.addLast(friend_node.node_id);
}
}
//找到离tree_node_id点最远的点
TreeNode max_node = tree_node_list[1];
for(int i = 1; i < tree_node_list.length; i++) {
if(tree_node_list[i].dist > max_node.dist) {
max_node = tree_node_list[i];
}
}
return max_node.node_id;
}
public int getMaxDist(TreeNode[] tree_node_list) {
int tmp_node_id = getMaxDistTreeNode(tree_node_list, 1);//找到离1节点最远的点
int max_dist_node_id = getMaxDistTreeNode(tree_node_list, tmp_node_id); //找到离tmp_node_id最远的点
return tree_node_list[max_dist_node_id].dist;
}
上面所写的代码就是求解树的直径的代码,接下来我们构造原题中的样例。
利用树的直径求解该问题的代码
//初始化
TreeNode[] tree_node_list = new TreeNode[4];
for(int i = 1; i < 4; i++) {
tree_node_list[i] = new TreeNode();
tree_node_list[i].node_id = i;
}
//添加边连接关系
tree_node_list[1].friends.add(tree_node_list[2]);
tree_node_list[2].friends.add(tree_node_list[1]);
tree_node_list[2].friends.add(tree_node_list[3]);
tree_node_list[3].friends.add(tree_node_list[2]);
//求树的直径
int max_dist = getMaxDist(tree_node_list);
System.out.println("max dist: " + max_dist);
//求解最终答案
int ans_step = 0;
int tmp_dist = max_dist;
while(tmp_dist > 1) {
if(tmp_dist % 2 == 0) {
tmp_dist = tmp_dist / 2;
} else {
tmp_dist = tmp_dist / 2 + 1;
}
ans_step += 1;
};
System.out.println("demo ans: " + ans_step);
max dist: 2
demo ans: 1