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

强连通缩点学习小结-附加两个强连通缩点题poj2186、hdu2767

2017年02月22日 ⁄ 综合 ⁄ 共 3776字 ⁄ 字号 评论关闭

在学习了tarjan算法求解强连通分量之后就接触到强连通缩点,但是就是不知道怎么运用tarjan算法来找缩点,后来接触了几个有关缩点的题目,才了解到缩点的关键所在;

对于一个图,我们进行强连通分量求解之后,进行缩点操作,缩点的最大好处在于把一个杂乱无章的有向图变成一个有向无环图,而在有向无环图中,有两种点比较特殊:一种是入度为 0 的点,另一种是 出度为 0 的点。我们把入度为0的点就叫做根,出度为0的点叫做叶子,可以参见下图

对于图中的绿色点就是叶子,红色的点就是根,未标记的就是中间点,但是注意:这里是缩点之后形成的有向无环图,图中的每一个点就是一个强连通分量,那么我们怎么把一个图缩点成为一个有向无环图呢?那么这里就用到一个数组来“染色”,我们在求强连通分量的时候有一个
Bcnt 用来记录强连通分量个数,有一个数组Belong[MAX]来染色,Belong[i] =Bcnt 就表示 i 这个元素属于第Bcnt个强连通分量,在把所有的点求完强连通分量之后,我们只是得到一个所有点的归属数组Belong[MAX],那么,之后我们就用两个数组 in[MAX],out[MAX]表示缩点后的有向无环图的点的入度和出度,用以下代码求解缩点形成的图

memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
for(i = 1; i <= n; i ++)
{
	for(int k = head[i]; k != -1; k = edge[k].next)
	{
		j = edge[k].to;
		if(Belong[i] != Belong[j])//注意这里就是所属的强连通分量,也就是属于哪一个缩点
			out[Belong[i]] ++, in[Belong[j]] ++;
	}
}

那么我们就得到这个有向无环图的关系,那么下面给出两个有关缩点的题目

poj2186Popular cow

题意:有N头牛,M个关系,每个关系为 a b,表示牛a认为牛b 收欢迎,那么问根据所给信息判断有多少头牛是收到所有的牛的欢迎,而且这里a认为b受欢迎,b认为c受欢迎,那么a也会认为c受欢迎,

分析:这里我们根据M个关系建图,然后对于这个图缩点,那么,根据上面的缩点分析,我们只要求叶子的个数,因为别的缩点都指向叶子,叶子处在最高层,就是最受欢迎的,那么我们根据所有缩点的out[i]=0的个数,判断,如果只有一个,那么这个缩点(强连通分量)的牛都是最受欢迎的,(这里注意,叶子数为0就是表示只有一个强连通分量,那么所有的牛都是最受欢迎的),如果有多个就输出0

那么直接上马,马上在来点注释:

// 708K 47MS 
#include<stdio.h>
#include<string.h>

#define MAX 10001

int N,M;
//建图用到的
struct Edge
{
	int to,next;
}edge[MAX*5];
int head[MAX],tol;
void add(int a,int b)
{
	edge[tol].to = b;
	edge[tol].next = head[a];
	head[a] = tol++;
}

//tarjan
int Belong[MAX],dfn[MAX],low[MAX],stack[MAX],time,Bcnt,top;
bool instack[MAX];
int out[MAX];//记录出度

void tarjan(int u)
{
	int v;
	dfn[u] = low[u] = ++time;
	stack[top ++] = u;
	instack[u] = true;
	for(int j = head[u]; j != -1; j = edge[j].next)
	{
		v = edge[j].to;
		if(!dfn[v])
		{
			tarjan(v);
			if(low[v] < low[u])
				low[u] = low[v];
		}
		else if(instack[v] && dfn[v] < low[u])
			low[u] = dfn[v];
	}
	if(dfn[u] == low[u])
	{
		Bcnt ++;
		do
		{
			v = stack[--top];
			instack[v] = false;
			Belong[v] = Bcnt;
		}while(u != v);
	}
}

int main()
{
	int u,v;
	int a,b;//输入要用到的a,b
	scanf("%d%d",&N,&M);

	// 建图
	tol = 0;
	memset(head,-1,sizeof(head));
	while(M --)
	{
		scanf("%d%d",&a,&b);
		add(a,b);
	}

	//tarjan
	memset(dfn,0,sizeof(dfn));
	Bcnt = top = time = 0;
	for(u = 1; u <= N; u ++)
		if(!dfn[u])
			tarjan(u);

	//tarjan之后求缩点的出度
	memset(out,0,sizeof(out));
	for(u = 1; u <= N; u ++)
	{
		for(int j = head[u]; j != -1; j = edge[j].next)
		{
			v = edge[j].to;
			if(Belong[u] != Belong[v])
				out[Belong[u]] ++;
		}
	}
	int num = 0,ans = 0,flag;//num记录缩点后出度为0的个数,flag记录入度为0的缩点标号(也就是第几个个联通分量)
	for(int i = 1; i <= Bcnt; i ++)
		if(out[i] == 0)
			num ++,flag = i;
	if(num == 1)//只有一个联通分量
	{
		for(int i = 1; i <= N; i ++)
			if(Belong[i] == flag)//这里计算属于这个连通分量的所有牛
				ans ++;
		printf("%d\n",ans);
	}
	else
		printf("0\n");
	return 0;
}

hdu2767

题意:这里有一个东西要你证明,就是有n个式子,用1到n标记,有m个关系,每个关系为a b 表示a推导出b,那么我们要这n个式子都是等价的最少还需要多少个关系

分析:这里我们把n个式子看做n个点,那么要是n个点等价就是任意一个点能推导出任意的另一个点,意思就是最后要是一个强连通,问最少要添加多少条边,我们根据已有的关系建图之后,强连通缩点,然后我们分别求叶子和根的数量,那么最多的那个就是我们要的答案,但是当缩点只有一个点的时候,答案是 0

上马

//125MS 1652K 
#include<stdio.h>
#include<string.h>

#define MAX 10001

int n,m;

//前向星
struct Edge
{
	int to,next;
}edge[MAX*5];
int head[MAX*2],tol;
void add(int a,int b)
{
	edge[tol].to = b;
	edge[tol].next = head[a];
	head[a] = tol ++;
}

//tarjan算法
int dfn[MAX*2],low[MAX*2],stack[MAX*2],Belong[MAX*2],time,top,Bcnt;
bool instack[MAX*2];
int in[MAX*2],out[MAX*2];//记录缩点后的入度出度
void tarjan(int u)
{
	int v;
	dfn[u] = low[u] = ++time;
	stack[top ++] = u;
	instack[u] = true;
	for(int j = head[u] ; j != -1; j = edge[j].next)
	{
		v = edge[j].to;
		if(!dfn[v])
		{
			tarjan(v);
			if(low[v] < low[u])
				low[u] = low[v];
		}
		else if(instack[v] && dfn[v] <low[u])
			low[u] = dfn[v];
	}
	if(dfn[u] == low[u])
	{
		Bcnt ++;
		do
		{
			v = stack[-- top];
			instack[v] = false;
			Belong[v] = Bcnt;
		}while(v != u);
	}
}

int main()
{
	int a,b,i,j;
	int T;
	scanf("%d",&T);
	while(T --)
	{
		scanf("%d%d",&n,&m);

		//建图
		tol = 0;
		memset(head,-1,sizeof(head));
		while(m --)
		{
			scanf("%d%d",&a,&b);
			add(a,b);
		}

		//缩点
		Bcnt = time = top = 0;
		memset(dfn,0,sizeof(dfn));
		for(i = 1; i <= n; i ++)
			if(!dfn[i])
				tarjan(i);
		if(Bcnt == 1)
		{
			printf("0\n");continue;
		}
		memset(in,0,sizeof(in));
		memset(out,0,sizeof(out));
		for(i = 1; i <= n; i ++)
		{
			for(int k = head[i]; k != -1; k = edge[k].next)
			{
				j = edge[k].to;
				if(Belong[i] != Belong[j])
					out[Belong[i]] ++, in[Belong[j]] ++;
			}
		}
		
		//缩点之后找叶子和根的数量
		int root = 0,leaf = 0;
		for(i = 1; i <= Bcnt; i ++)
		{
			if(in[i] == 0) root ++;
			if(out[i] == 0) leaf ++;
		}
		printf("%d\n",leaf > root ? leaf : root);
	}
	return 0;
}

个人愚昧观点,欢迎指正与讨论

抱歉!评论已关闭.