POJ 2186 popular cow 有向图的强联通问题 Tarjan算法

来源:互联网 发布:孕妇孕期必备软件 编辑:程序博客网 时间:2024/06/04 18:46

参考:http://hi.baidu.com/1093782566/blog/item/e5a0e9229913bd048b82a175.html

http://www.cppblog.com/IronOxide/archive/2010/08/16/123622.html?opt=admin


题目简述:n头奶牛,给出若干个欢迎关系a b,表示a欢迎b,欢迎关系是单向的,但是是可以传递的。另外每个奶牛都是欢迎他自己的。求出被所有的奶牛欢迎的奶牛的数目。
模型转换:N个顶点的有向图,有M条边(N≤10000,M≤50000)。求一共有多少个点,满足这样的条件:所有其它的点都可以到达这个点。
首先,这个题的N和M都非常大,硬做是肯定不行的。考虑如果这个图是一棵树,那么问题就变的很简单了,因为至多有一个点满足条件,这个点满足条件的充要条件是:这个点是树中唯一的出度为0的点。

那么我们能否把图转化为树呢?首先可以想到的是,如果图中包含有环,那么就可以把这个环缩成一个点,因为环中的任意两个点可以到达,环中所有的点具有相同的性质,即它们分别能到达的点集都是相同的,能够到达它们的点集也是相同的。缩点后的图必无环,否则,可将环上所有点也缩成一个点,与极大强联通分量矛盾。


那么是否只有环中的点才具有相同的性质呢?进一步的考虑,图中的每一个极大强连通分支中的点都具有相同的性质。所以,如果把图中的所有极大强连通分支求出后,就可以把图收缩成一棵树,问题就迎刃而解了。

预备知识:有向图的强连通分量的求法,这个和求割点的算法差不多。
算法框架:对有向图求强连通分量,然后找出所有独立的强连通分量(所谓独立,就是该连通分量里面的点到外面的点没有通路,当然,连通分量外的点是可以有路到强连通分量内的点的),如果独立的强连通分量的数目只有一个,那么,就输出这个强连通分量内解的个数,否则输出无解。只要找到缩点后的图中无出度的点的个数,设为cnt, 若 cnt > 1 , 则必无满足条件的点,因为一个出度为

零的点无法到达另一个出度为零的点;若cnt = 1 , 则该点所对应的强联通分量的点的个数即为答案。


算法证明:
1:假设a和b都是最受欢迎的cow,那么,a欢迎b,而且b欢迎a,于是,a和b是属于同一个连通分量内的点,所有,问题的解集构成一个强连通分量。
2:如果某个强连通分量内的点a到强连通分量外的点b有通路,因为b和a不是同一个强连通分量内的点,所以b到a一定没有通路,那么a不被b欢迎,于是a所在的连通分量一定不是解集的那个连通分量。
3:如果存在两个独立的强连通分量a和b,那么a内的点和b内的点一定不能互相到达,那么,无论是a还是b都不是解集的那个连通分量,问题保证无解。

4:如果图非连通,那么,至少存在两个独立的连通分量,问题一定无解。

[cpp] view plaincopy
  1. #include <iostream>  
  2. #include <stack>  
  3. #include <cstring>  
  4. using namespace std;  
  5.   
  6. const int MAXN = 10000 + 10;     // 点的最大数量  
  7. const int MAXM = 50000 + 10;     // 边的最大数量  
  8.   
  9. // 假设对边u-->v  
  10. struct EDGE  
  11. {  
  12.  int v;                    // 从u点出发能到达的点v  
  13.  int next;                 // 从u点出发能到达的下一条边的编号  
  14. };  
  15.   
  16. stack<int> s;  
  17. EDGE edge[MAXM];  
  18. int low[MAXN];             // low[u]:是u或u的子树能够追溯到的最早的栈中节点的次序号  
  19. int dfn[MAXN];             // dfn[u]:节点u搜索的次序编号(时间戳)  
  20. int first[MAXN];             // first[u] = e:从点u出发的最后一条边的编号是e(“最后”是指最后输入)  
  21. int sccf[MAXN];            // sccf[i] = j:第i个点所在的强连通分量的编号  
  22. bool ins[MAXN];            // 是否在栈中  
  23. int outdegree[MAXN];       // 强连通分量的出度  
  24. int index;                 // 次序编号  
  25. int scc;                   // 强连通分量的数目  
  26. int n, m;  
  27.   
  28.   
  29. void Init()  
  30. {  
  31.     scc = 0;  
  32.     index = 1;  
  33.     memset(low, 0, sizeof(low));  
  34.     memset(dfn, 0, sizeof(dfn));  
  35.     memset(ins, falsesizeof(ins));  
  36.     memset(sccf, 0, sizeof(sccf));  
  37.     memset(first, -1, sizeof(first));  
  38. }  
  39.   
  40. void Tarjan(int u)  
  41. {  
  42.     int v;  
  43.     low[u] = dfn[u] = index++;  
  44.     s.push(u);  
  45.     ins[u] = true;  
  46.     // 枚举每一条边:u-->v  
  47.     for (int k=first[u]; k!=-1; k=edge[k].next)  
  48.     {  
  49.         v = edge[k].v;  
  50.         if (dfn[v] == 0)  
  51.         {  
  52.             Tarjan(v);  
  53.             low[u]=min(low[u],low[v]);  
  54.         }  
  55.         else if (ins[v])  
  56.         {  
  57.             low[u]=min(low[u],dfn[v]);  
  58.         }  
  59.     }  
  60.     // 如果节点u是强连通分量的根  
  61.     if (dfn[u] == low[u])  
  62.     {  
  63.         scc++;  
  64.         do  
  65.         {  
  66.             v = s.top();  
  67.             s.pop();  
  68.             ins[v] = false;  
  69.             sccf[v] = scc;  
  70.         }while (u != v);  
  71.     }  
  72. }  
  73.   
  74.   
  75.   
  76.   
  77.   
  78. // 获得超级受喜欢的cows的数量  
  79.   
  80. int GetSuperPopularNum()  
  81. {  
  82.     int u, v;  
  83.     int cnt = 0;    // 出度为0的强连通分量的数目  
  84.     int ct[MAXN];    // ct[i] = j:强连通分量i有j个点  
  85.   
  86.     memset(outdegree, 0, sizeof(outdegree));  
  87.     memset(ct, 0, sizeof(ct));  
  88.   
  89.   
  90.   
  91.     // 枚举每一个点u:求outdegree和ct  
  92.     for (u=1; u<=n; u++)  
  93.     {  
  94.         ct[sccf[u]]++;  
  95.         for (int k=first[u]; k!=-1; k=edge[k].next)  
  96.         {  
  97.             // 对每条边u-->v  
  98.             v = edge[k].v;  
  99.             if (sccf[u] != sccf[v])  
  100.             {  
  101.                 outdegree[sccf[u]]++;  
  102.             }  
  103.         }  
  104.     }  
  105.   
  106.     // 数数强连通分量为0的点有多少个  
  107.     for (u=1; u<=scc; u++)  
  108.     {  
  109.         if (outdegree[u] == 0)  
  110.         {  
  111.             cnt++;  
  112.             v = u;  
  113.         }  
  114.     }  
  115.   
  116.     return (cnt == 1)? ct[v] : 0;  
  117. }  
  118.   
  119.   
  120.   
  121. int main()  
  122. {  
  123.     int i, u, v;  
  124.     int e = 0;      // 边的数量,建图时会用到  
  125.   
  126.     // 初始化数据并建图  
  127.     Init();  
  128.     cin >> n >> m;  
  129.     for (i=0; i<m; i++)  
  130.     {  
  131.         cin >> u >> v;  
  132.         edge[e].v = v;  
  133.         edge[e].next = first[u];  
  134.         first[u] = e;  
  135.         e++;  
  136.     }  
  137.   
  138.     // 求强连通分量  
  139.     for (i=1; i<=n; i++)  
  140.     {  
  141.         if (dfn[i] == 0)  
  142.         {  
  143.             Tarjan(i);  
  144.         }  
  145.     }  
  146.   
  147.     // 输出答案  
  148.     cout << GetSuperPopularNum() << endl;  
  149.   
  150.     return 0;  
  151. }  
0 0
原创粉丝点击