• PAT 甲级 A1021 Deepest Root


    A graph which is connected and acyclic can be considered a tree. The height of the tree depends on the selected root. Now you are supposed to find the root that results in a highest tree. Such a root is called the deepest root.

    Input Specification:

    Each input file contains one test case. For each case, the first line contains a positive integer N (≤104) which is the number of nodes, and hence the nodes are numbered from 1 to N. Then N−1 lines follow, each describes an edge by given the two adjacent nodes' numbers.

    Output Specification:

    For each test case, print each of the deepest roots in a line. If such a root is not unique, print them in increasing order of their numbers. In case that the given graph is not a tree, print Error: K components where K is the number of connected components in the graph.

    Sample Input 1:

    1. 5
    2. 1 2
    3. 1 3
    4. 1 4
    5. 2 5

    Sample Output 1:

    1. 3
    2. 4
    3. 5

    Sample Input 2:

    1. 5
    2. 1 3
    3. 1 4
    4. 2 5
    5. 3 4

    Sample Output 2:

    Error: 2 components

    题意:

    给出n个结点,和n-1个边,问这些点和边能否构成一棵树, 如果能求这颗树的最大高度(无向图,根节点不同,其高度也不同),如果不能输出连通块的个数。

    思路:
    有n-1条边且连通的图一定是树,我们需要的就是遍历图所有的节点,找到连通块的个数,不止一块的话肯定不是树,如果只有一个连通块且边数是节点数减一,就肯定是树了,再遍历这个图,找到最大深度,和满足最大深度的叶子结点,将其放到temp里面,这些就是满足最大深度的根节点编号,但是并不是最终答案,最终答案需要再以temp中随机的一个根节点作为开始,反向再遍历这个图,将满足条件的结点再放到temp里面,这个才是最终的答案,意思就是正向要遍历一次,满足条件的结点作为根节点再遍历一次。

    代码:
     

    1. //并查集遍历图找出连通块,判断是否为树;
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. const int maxn = 10010;
    7. int father[maxn] = {0};
    8. bool vis[maxn] = {false};
    9. vector<int > node[maxn];
    10. int n;
    11. //并查集三件套;
    12. void init() {//初始化
    13. for(int i = 1; i <= n ;i++) {
    14. vis[i] = false;
    15. father[i] = i;
    16. }
    17. return ;
    18. }
    19. int findRoot(int x) {
    20. if(x == father[x]) return x;
    21. else {//路径压缩;
    22. int root = findRoot(father[x]);
    23. father[x] = root;
    24. return root;
    25. }
    26. }
    27. void Union(int a, int b) {
    28. int rootA = findRoot(a);
    29. int rootB = findRoot(b);
    30. if(rootA != rootB) {
    31. father[rootB] = rootA;
    32. }
    33. return ;
    34. }
    35. int maxDepth = 0;
    36. vector<int > temp, ans;
    37. //深度优先搜索找深度最高的叶子结点,反过来也可以当作根节点;
    38. void DFS(int nowNode, int depth, int pre) {
    39. if(depth > maxDepth) {//找到最大深度;
    40. temp.clear();
    41. temp.push_back(nowNode);
    42. maxDepth = depth;
    43. }else if(maxDepth == depth) {//最大深度的根节点不可能是中间结点;
    44. temp.push_back(nowNode);
    45. }
    46. for(int i = 0 ; i < node[nowNode].size(); i++) {//不要把i当作nowNode的孩子,其孩子应该是node[nowNode][i];
    47. if(node[nowNode][i] == pre) continue;//因为是无权图,互指,所以防止循环走回头路;
    48. DFS(node[nowNode][i], depth + 1, nowNode);//遍历孩子找出最高深度;
    49. }
    50. }
    51. int main () {
    52. scanf("%d", &n);
    53. for(int i = 0; i < n-1; i++) {
    54. int a, b;
    55. scanf("%d %d", &a, &b);
    56. node[a].push_back(b);
    57. node[b].push_back(a);
    58. }
    59. init();//初始化;
    60. for(int i = 1; i <= n; i++) {
    61. for(int j = 0; j < node[i].size(); j++) {
    62. int u = i, v = node[i][j];
    63. Union(u, v);//一条边上的两个结点连到一个集合里面;
    64. }
    65. }//找出了所有的连通块;
    66. int block = 0;
    67. for(int i = 1; i <= n; i++) {
    68. if(vis[findRoot(i)] == false ){
    69. block++;//找出集合(连通块的个数);
    70. vis[findRoot(i)] = true;//i所在的集合标记;
    71. }
    72. }
    73. if(block != 1) {
    74. printf("Error: %d components",block);//不止一块连通块;
    75. }else {//只有一块连通块且节点数 = 边数+1,则说明是树;
    76. //-1是前驱结点,因为是无权图,防止走回头路;
    77. DFS(1, 1, -1);//找出最大深度和最大深度时的根集合ans;
    78. ans = temp;//vector可以直接赋值;
    79. DFS(ans[0], 1, -1);//以ans的某个结点为根节点再遍历一次得到集合b,这样找两遍才能得到答案,不然只有部分答案;
    80. for(int i = 0; i < temp.size(); i++) {
    81. ans.push_back(temp[i]);
    82. }
    83. sort(ans.begin(), ans.end());
    84. printf("%d\n", ans[0]);
    85. for(int i = 1; i < ans.size(); i++) {
    86. if(ans[i] != ans[i - 1]) {
    87. printf("%d\n", ans[i]);
    88. }
    89. }
    90. }
    91. return 0;
    92. }

    三个命题:
    ①从任意顶点x出发遍历树,得到的最深结点一定是所求根节点的一部分(一侧)。

    也就是说从任何一个结点出发,最深的那个叶子结点一定是可以使得这棵树最大深度的根节点。

    证明:

    假设树有直径RL(树的最大深度叫做直径),从R出发经过O到达L,RL长度最长。

    前提条件(且一定满足的,因为树一定有最深深度):树的最大直径为RL。

    假设:从某一个结点X出发,同样经过O遍历到了最深深度叶子结点Y,Y既不是R也不是O

    证明:因为同经过一个结点O,根据前提条件RL最大,应有RO和OL最大,所以XO是比RO小的,所有就有OY>OL,所有就又RL

    结论:所以就由从任何结点x进行树的遍历,得到的最深结点一定是R或者L,其中的一个,这样才能保证树的最大高度是定值,即所求根节点集合的一部分。

    ②树中的所有直径肯定是相交于一个点或者一个公共重合区间(线段)。

    假设:树中的两条直径不相交

    证明:树是连通图,两条直径肯定中间肯定有线段与之相连,使其连通,因此有树中的直径肯定相交于同一点或者公共重合区间。

    ③两次遍历的结果的并集为所求的根节点的结合。

    一次遍历只能得到直径交点的一侧的答案,还要再根据答案反着遍历一次树才能得到答案。

  • 相关阅读:
    VSCode 使用CMakePreset找不到cl.exe编译器的问题
    马上快毕业三年了,感觉很空洞
    微信撤回时间延长至3小时,真的?
    内网渗透面试问题
    CSDN博客运营团队2022年H1总结
    22.10.29 CF-1294C
    GCC是什么?
    基于Java的家政公司服务平台设计与实现(源码+lw+部署文档+讲解等)
    4.2 实现基于栈的表达式求值计算器(难度4/10)
    【毕业设计】机器视觉停车位识别检测系统 - python 深度学习
  • 原文地址:https://blog.csdn.net/m0_51711089/article/details/126019625