校园网络
时间限制:
3000 ms | 内存限制:
65535 KB
难度:
5
-
描述
-
南阳理工学院共有M个系,分别编号1~M,其中各个系之间达成有一定的协议,如果某系有新软件可用时,该系将允许一些其它的系复制并使用该软件。但该允许关系是单向的,即:A系允许B系使用A的软件时,B未必一定允许A使用B的软件。
现在,请你写一个程序,根据各个系之间达成的协议情况,计算出最少需要添加多少个两系之间的这种允许关系,才能使任何一个系有软件使用的时候,其它所有系也都有软件可用。
在
有向图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算法。
接下来是对算法流程的演示。
从节点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;
}