您的位置:首页 > 理论基础 > 计算机网络

NYOJ 120 校园网络(强连通分量缩点)

2014-10-01 16:59 253 查看


校园网络

时间限制:3000 ms  |  内存限制:65535 KB
难度:5

描述

南阳理工学院共有M个系,分别编号1~M,其中各个系之间达成有一定的协议,如果某系有新软件可用时,该系将允许一些其它的系复制并使用该软件。但该允许关系是单向的,即:A系允许B系使用A的软件时,B未必一定允许A使用B的软件。

现在,请你写一个程序,根据各个系之间达成的协议情况,计算出最少需要添加多少个两系之间的这种允许关系,才能使任何一个系有软件使用的时候,其它所有系也都有软件可用。

输入第一行输入一个整数T,表示测试数据的组数(T<10)

每组测试数据的第一行是一个整数M,表示共有M个系(2<=M<=100)。

随后的M行,每行都有一些整数,其中的第i行表示系i允许这几个系复制并使用系i的软件。每行结尾都是一个0,表示本行输入结束。如果某个系不允许其它任何系使用该系软件,则本行只有一个0.

输出对于每组测试数据,输出最少需要添加的这种允许关系的个数。
样例输入
1
5
2 4 3 0
4 5 0
0
0
1 0


样例输出
2


来源POJ改编tarjan算法介绍(求有向图的强连通分量的)

有向图G中,如果两个顶点可以相互通达,则称两个顶点强连通(strongly
connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly
connected components)。

下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。

Tarjan算法是用来求有向图的强连通分量的。求有向图的强连通分量的Tarjan算法是以其发明者Robert
Tarjan命名的。Robert Tarjan还发明了求双连通分量的Tarjan算法。

Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量

定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。

DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量

接下来是对算法流程的演示。

从节点1开始DFS,把遍历到的节点加入栈中。搜索到节点u=6时,DFN[6]=LOW[6],找到了一个强连通分量。退栈到u=v为止,{6}为一个强连通分量。





返回节点5,发现DFN[5]=LOW[5],退栈后{5}为一个强连通分量。





返回节点3,继续搜索到节点4,把4加入堆栈。发现节点4向节点1有后向边,节点1还在栈中,所以LOW[4]=1。节点6已经出栈,(4,6)是横叉边,返回3,(3,4)为树枝边,所以LOW[3]=LOW[4]=1。





继续回到节点1,最后访问节点2。访问边(2,4),4还在栈中,所以LOW[2]=DFN[4]=5。返回1后,发现DFN[1]=LOW[1],把栈中节点全部取出,组成一个连通分量{1,3,4,2}。





至此,算法结束。经过该算法,求出了图中全部的三个强连通分量{1,3,4,2},{5},{6}。

可以发现,运行Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(N+M)。

其实这道题说的是强连通缩点就是把只有各个连通图的根节点留下,其他去掉,让几个根节点连通来求解

// 强连通分量缩点

#include<cstdio>

#include<iostream>

#include<algorithm>

#include<stack> 

#include<cstring>

using namespace std;

int map[105][105];

int DNF[105],low[105],IN[105],OUT[105],t[105],instack[105];

int min(int a,int b){return a > b ? b : a;} 

int V,res,order; 

stack<int> S; 

int tarjan(int u)

{

   int v; 

   DNF[u] = low[u] = ++order;

   instack[u]=1;

   S.push(u);

   for(int i = 1; i <= V; i++){

       if(map[u][i]){

           if(!DNF[i]){

               tarjan(i);                            

               low[u]=min(low[u],low[i]); 

           }      

           else if(instack[i])low[u]=min(low[u],DNF[i]);   

        }   

   }    

   if(DNF[u]==low[u]){

        ++res;             // res 代表强连通分量的个数

        do{

           v = S.top();

           S.pop();  

           instack[v]=0;

           t[v]=res;     

        }while(v != u);                     

   }    



int main()



     int test;   

     scanf("%d",&test);

     while(test--){                               

          int x; 

          memset(map, 0, sizeof(map));        //用矩阵存放数据          

          memset(DNF, 0, sizeof(DNF));        // 节点搜索的次序编号   

          memset(low, 0, sizeof(low));         //记录强连通分量的根节点 

          memset(IN, 0, sizeof(IN));           //统计入度           

          memset(OUT, 0, sizeof(OUT));         //统计出度             

          memset(t, 0, sizeof(t));                         

          memset(instack, 0, sizeof(instack));   //判断节点是否在栈中

          while(!S.empty())

                S.pop();

          res=0;

          order=0; 

          scanf("%d",&V);

          for(int i = 1; i <= V; i++){

              while(~scanf("%d",&x)&&x) 

                    map[i][x] =  1;               

          }              

          for(int i = 1;i <= V;i++){

                 if(!DNF[i])tarjan(i);        

          }           

        //使原有的图经过强连通分量缩点变成只有res个强连通分量的节点的图

          for(int i = 1; i <= V; i++){

              for(int j = 1; j <= V; j++){

                  if(map[i][j]){       // 统计每个强连通分量缩点的入度和出度

                       ++IN[t[i]];              

                       ++OUT[t[j]]; 

                  }        

              }               

          }      

          int m=0,n=0;

          for(int i = 1; i <= res; i++){

                if(IN[i]==0)m++;

                else if(OUT[i]==0)n++;        

          }      

          int count = m > n? m : n;  // 结果为缩点后的有向图中出度为0或者入度为0中的大者

          

          if(res==1)printf("0\n"); 

          else printf("%d\n",count); 

     }  

     return 0;   

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: