這是一個有趣的題目,我們來詳細分析題目的解法
描述
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