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

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

2014-12-17 20:57 225 查看


校园网络

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




像上面的有向图。因为强连通分量中任意两点可以通过某条路径连通,也就是说某个点有新软件可用,强连通分量中的其它点必定可通过传递来使用,所以把所有强连通分量视为一个点。至于加边,就是把强连通分量缩点后的图变成环。只需要寻找入度为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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息