NYOJ-120校园网络【强连通分量缩点&&tarjan】

校园网络

时间限制: 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


NYOJ-120校园网络【强连通分量缩点&&tarjan】_第1张图片

像上面的有向图。因为强连通分量中任意两点可以通过某条路径连通,也就是说某个点有新软件可用,强连通分量中的其它点必定可通过传递来使用,所以把所有强连通分量视为一个点。至于加边,就是把强连通分量缩点后的图变成环。只需要寻找入度为0的点和出度为0的点,两者中数量最大值即为答案。

有两种求强连通分量的算法。

(1)可以通过两次简单的DFS实现。第一次DFS时,选取任意顶点,作为起点,遍历所有尚未访问过的顶点,并在回溯前给顶点标号(后序遍历)。完成标号后,将所有边反向,然后以标号最大的顶点作为起点DFS。这样DFS所遍历的顶点集合就变成了一个强连通分量。之后,只要还有尚未访问的顶点,就从中选取标号最大的顶点不断重复上述过程。

(2)tarjan算法:Tarjan算法基于定理:在任何深度优先搜索中,同一强连通分量内的所有顶点均在同一棵深度优先搜索树中。也就是说,强连通分量一定是有向图的某个深搜树子树。
可以证明,当一个点既是强连通子图Ⅰ中的点,又是强连通子图Ⅱ中的点,则它是强连通子图Ⅰ∪Ⅱ中的点。
这样,我们用low值记录该点所在强连通子图对应的搜索子树的根节点的Dfn值。注意,该子树中的元素在栈中一定是相邻的,且根节点在栈中一定位于所有子树元素的最下方。强连通分量是由若干个环组成的。所以,当有环形成时(也就是搜索的下一个点已在栈中),我们将这一条路径的low值统一,即这条路径上的点属于同一个强连通分量。
如果遍历完整个搜索树后某个点的dfn值等于low值,则它是该搜索子树的根。这时,它以上(包括它自己)一直到栈顶的所有元素组成一个强连通分量。

#include <stdio.h>
#include <string.h>
#include <stack>
#define Minn(a,b) a>b?b:a
#define Maxn(a,b) a>b?a:b
using namespace std;
int M;
bool map[106][106];//用邻接矩阵来存边方便计算出入度
bool mark[106];//标记数组
int d[300];
int p[106];
int low[106];
int dfn[106];
int num;
int count;
int inum,onum;//入度为0的点 出度为0的点
stack<int> sta;
void tarjan(int x)//tarjan算法
{
	int i;
	low[x]=dfn[x]=++count;
	sta.push(x);
	mark[x]=1;
	for(i=1;i<=M;i++)
	{
		if(map[x][i]==1)
		{
			if(!dfn[i])
			{
				tarjan(i);
				low[x]=Minn(low[x],low[i]);
			}
			else if(mark[i]==1)
				low[x]=Minn(low[x],dfn[i]);
		}
	}
	if(low[x]==dfn[x])
	{//我把所有栈里弹出的属于同一强连通分量的点都编号为num,++num可以使不同强连通分量的编号不同。
		int v;
		int t=++num;
		do
		{//弹出的点暂时存在d数组中。p数组存储该点所属的强连通分量的编号
			v=sta.top();sta.pop();mark[v]=0;
			d[t++]=v;p[v]=num;
		}while(low[v]!=dfn[v]);
		if(t-num==M)//如果说图就是个强连通图直接返回。
			return;
		int id=0,od=0;
		for(t=t-1;t>=num;t--)
		{//邻接矩阵里计算该强连通缩点的出入度
			v=d[t];
			for(i=1;i<=M;i++)
			{
				if(map[v][i]==1&&p[v]!=p[i])
					od++;
				if(map[i][v]==1&&p[v]!=p[i])
					id++;
			}
		}
		if(od==0)
			onum++;
		if(id==0)
			inum++;
	}
}
int main()
{
	int T;
	int i;
	int ch;
	int ans;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&M);
		memset(map,0,sizeof(map));
		for(i=1;i<=M;i++)
		{
			scanf("%d",&ch);
			while(ch!=0)
				{map[i][ch]=1;scanf("%d",&ch);}
		}
		memset(dfn,0,sizeof(dfn));
		memset(mark,0,sizeof(mark));
		memset(p,0,sizeof(p));
		count=0;num=0;
		inum=onum=0;
		for(i=1;i<=M;i++)
		{
			if(!dfn[i])
				tarjan(i);
		}
		ans=Maxn(inum,onum);
		printf("%d\n",ans);
	}
	return 0;
}


你可能感兴趣的:(强连通分量,nyoj,tarjan算法)