现在的位置: 首页 > 综合 > 正文

poj-1236-一起学习强连通分量

2013年09月15日 ⁄ 综合 ⁄ 共 2278字 ⁄ 字号 评论关闭
文章目录

题目不是很好理解,简单说一下

就是有一个有向图,第一问求至少要复制几次软件,才能保证每个地方都有,实际上就是求入度为0的,也就是走不到的。第二问求至少添加几个扩展,也即是添加几条边,让无论从那个地方,都能让任何地方得到软件。也就是添加几条边,能让整个图强连通。

 

强连通分量算法1---kosaraju最好理解的两次dfs。

步骤概要:

1. DFS有向图G,并以后根序记录节点

2. 把存在于记录集中且最后访问节点作为起点,DFS反图GT,并以先根序把节点从记录中剔除;

3. 若此次不能DFS反图GT所有节点,则重复步骤2,直到所有节点都被剔除出记录;每次剔除掉的节点集即为原有向图G的一个强连通分量

 

简要证明:

1. 第一次DFS有向图G时,最后记录下的节点必为最后一棵生成树的根节点。
证明:假设最后记录下节点不是树根,则必存在一节点为树根,且树根节点必为此节点祖先;而由后根序访问可知祖先节点比此节点更晚访问,矛盾;原命题成立

2. 第一次DFS的生成森林中,取两节点A、B,满足:B比A更晚记录下,且B不是A的祖先(即在第一次DFS中,A、B处于不同的生成树中);则在第二次DFS的生成森林中,B不是A的祖先,且A也不是B的祖先(即在第二次DFS中,A、B处于不同的生成树中)。

证明:假设在第二次DFS的生成森林中,B是A的祖先,则反图GT中存在B到A路径,即第一次DFS生成森林中,A是B的祖先,则A必比B更晚记录下,矛盾;假设在第二次DFS的生成森林中,A是B的祖先,则反图GT中存在A到B路径,即第一次DFS生成森林中,B是A的祖先,矛盾;原命题成立

3. 按上述步骤求出的必为强连通分量
证明:首先,证明2保证了第二次DFS中的每一棵树都是第一次DFS中的某棵树或某棵树的子树。其次,对于第二次DFS中的每棵树,第一次DFS保证了从根到其子孙的连通性,第二次DFS保证了根到子孙的反向连通性(即子孙到根的连通性);由此,此树中的每个节点都通过其根相互连通。

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define nMax 110
int map[nMax][nMax],mapT[nMax][nMax];
int belong[nMax];
int order[nMax];
bool vist[nMax];
int n,index,cnt;
int indegree[nMax],outdegree[nMax];

void firstDfs(int u)//第一次dfs,求出各点被搜索时的时间戳
{
	vist[u] = true;
	for (int i = 1; i <= n; ++ i)
	{
		if (map[u][i] && !vist[i])
		{
			firstDfs(i);
		}
	}
	order[++ index] = u;//记录每点被搜索时的时间戳
	//printf("%d\n", u);
}
void secondDfs(int u)//第二次dfs,找到每个强连通分量
{
	vist[u] = true;
	belong[u] = cnt;//记录u点属于哪个强连通分量cnt
	for (int i = 1; i <= n; ++ i)
	{
		if (!vist[i] && mapT[u][i])
		{
			secondDfs(i);
		}
	}
}
void kosaraju()
{
	index = 0;
	cnt = 0;
	memset(vist, false, sizeof(vist));
	memset(order, 0, sizeof(order));
	for (int i = 1; i <= n; ++ i)
	{
		if (!vist[i])
		{
			firstDfs(i);//dfs求各点时间戳
		}
	}
	memset(vist, false, sizeof(vist));
	for (int i = n; i >= 1; -- i)//倒着搜索找到各个强连通分量
	{
		if (!vist[order[i]])
		{
			cnt ++;//强连通分量的个数
			secondDfs(order[i]);
		}
	}

}
void output()
{
	int inNum = 0,outNum = 0;
	memset(indegree, 0, sizeof(indegree));
	memset(outdegree, 0, sizeof(outdegree));
	for (int i = 1; i <= n; ++ i)//求强连通分量的出度和入度
	{
		for (int j = 1; j <= n; ++ j)
		{
			if (map[i][j] && belong[i] != belong[j])
			{
				indegree[belong[j]] ++;
				outdegree[belong[i]] ++;
			}
		}
	}
	for (int i = 1; i <= cnt; ++ i)//求出出度和入度为0的点的个数
	{
		if (!indegree[i])
		{
			inNum ++;
		}
		if (!outdegree[i])
		{
			outNum ++;
		}
	}
	if (cnt == 1)//如果这个图强连通
	{
		printf("1\n0\n");
	}
	else//否则
		printf("%d\n%d\n",inNum, inNum > outNum ? inNum : outNum);
}
int main()
{
	while (scanf("%d", &n) != EOF)
	{
		memset(map, 0, sizeof(map));
		memset(mapT, 0, sizeof(mapT));
		for (int i = 1; i <= n; ++ i)
		{
			int v;
			while (scanf("%d", &v) && v)
			{
				map[i][v] = 1;//正向图邻接矩阵
				mapT[v][i] = 1;//逆向图邻接矩阵
			}
		}
		kosaraju();//kosaraju算法
		output();
	}
	return 0;
}

 

 

抱歉!评论已关闭.