1021. Deepest Root (25) DFS & (a little idea )

1021. Deepest Root (25)

时间限制
1500 ms
内存限制
65536 kB
代码长度限制
16000 B
判题程序
Standard
作者
CHEN, Yue

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 (<=10000) 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:
5
1 2
1 3
1 4
2 5
Sample Output 1:
3
4
5
Sample Input 2:
5
1 3
1 4
2 5
3 4
Sample Output 2:
Error: 2 components
    一道很有意思的题目,顺带让我回忆了一下高中的有机化学,然而你一定不知道为什么这会和高中有机化学扯上关系。
    先说一下题目大意,给你一个无向无环图,希望你找出其中的最长路,这道题最快只用两次DFS就够了,我用的是一个比较笨的方法,找到每个叶节点然后dfs求最长路并记录,最后将所有最长路为最大值的节点输出,在PAT上也能过,但是在牛客网上是过不了的(牛客网数据略强)。
    判断图是否连接用并查集解决,我觉得我不用多说的。然后图连通时,第一遍任取起点DFS用vis数组记录每个节点的在dfs搜索中的距离,与dfs能达到的最大距离Max,将所有vis[i]==Max的i插入set。第二遍从Set中任取一个元素,重复第一次遍历时的操作。然后输出set中的所有元素。
先上代码,后发证明。
# include <cstdio>
# include <iostream>
# include <cstring>
# include <algorithm>
# include <set>
# include <vector>
using namespace std;

const int size  = 10000; 
const int debug = 1;

class DisjointSet
{
private:
    int *T,size,sum;
    int FindRoot(int i){return T[i] < 0 ? i : (T[i] = FindRoot(T[i]));}
public:
    DisjointSet(int _size):size(_size)
    {
        T = new int[size];
        Init();
    }
    void Init(){sum = size;memset(T,-1,sizeof(int)*size);}
    bool Unioned(int i,int j){return FindRoot(i)==FindRoot(j);}
    void Union(int i,int j)
      {
        if ( (i = FindRoot(i) ) != ( j = FindRoot(j) ) )
        {
            T[i] = T[i] + T[j];
            T[j] = i;
            sum--;
        }
      }
    int FindSum(int i){return -T[FindRoot(i)];}
    int SumOfUnits(){return sum;}
    ~DisjointSet(){delete[] T;}
};

vector<int> v[size];
int vis[size];
void dfs(int i,int dist,int &MAX)
{
     vis[i] = dist;
     MAX = max(MAX,vis[i]);
     for (int k=0;k<v[i].size();k++)
         if (vis[v[i][k]]==-1)   
		    dfs(v[i][k],dist+1,MAX);
}
int main()
{
  int i,j,temp;
  int n;
  scanf("%d",&n);
  DisjointSet ds(n); 
  for (i=0;i<n-1;i++)
    {
      int a,b;
      scanf("%d%d",&a,&b);a--,b--;
      v[a].push_back(b);
      v[b].push_back(a);
      ds.Union(a,b);
    }
  if (ds.SumOfUnits() == 1)
    {
  	  int start,Max;
  	  set<int> s;
      for (Max=start=i=0;i<2;i++)
        {
       	  memset(vis,-1,sizeof(vis));
		  dfs(start,0,Max);
		  for (j=0;j<n;j++)
		      if (vis[j]==Max)
				  s.insert(j);
		  start = *s.begin();
		}
      for (set<int>::iterator it = s.begin();it!=s.end();it++)
	      printf("%d\n",(*it)+1);     
    }
  else 
      printf("Error: %d components\n",ds.SumOfUnits());
  return 0;
}
     
 这个算法有一个很玄奥的地方,就是两次遍历都有帅气逼人地用到了任取一词,为了说明算法的正确性,让我先上一张图
就是上面这幅图,圆的是顶点,长的是边,中间蓝色加省略号是省略省略不提的部分,然后红色是主干,也就是最长路,绿色是侧枝。
很容易理解对于任何一条侧枝 DE,存在|DE| < min(|AD|,|DC|),现在分类讨论,
      1. 对于第一次DFS选择的起点,如果是红色路径上的点,第一次DFS得到的最长路必定是距离较远的最长路顶点,在这幅图中就是说要么是A,要么是C,同时可以看到,假如起点是B,我们在DFS的时候E点的DFS深度也会是最长路径,也就是说我们选出了最起码一侧的所有最长路的端点。
      2.如果第一次选择的起点是侧枝上的点,如F (侧枝上还有侧枝的情况请自行脑补) ,F在进行DFS搜索的时候必定会经过D点,那么问题已经转化成了第一题的问题。
至于第二次DFS应该很好理解既然第一次选出了一端的所有顶点,第二次肯定会选出另一端所有顶点。
     至此该问题圆满解决。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值