POJ 1236 Network of Schools 强联通分量 + 缩点

来源:http://poj.org/problem?id=1236

题意:有一些学校,这些学校之间有一些边,边是单向边。现在有一套软件,如果一个学校有了这套软件,则该学校能到达的学校也就可以拥有这套软件。问至少需要几套软件,使得所有的学校都拥有软件,再问:至少需要添加几条边能够使得任意两个学校之间可达。

思路:其实就是一道强联通分量+缩点的模板题目。可以先算出强联通分量的数目,之后进行缩点。缩点后,统计出度为0的点的个数和入度为0的点的个数。则入度为0的点即为需要软件的数目,两者之间较大者即为需要添加边的数目。

说一下我对强联通分量的理解:

一个强联通分量指的是一个有向图内任意两点可达。我们可以用targin算法求强联通分量。targin算法中有两个数组,一个是low数组,一个是dfn数组。其中dfn数组表示的是访问这个点的时间,low数组比较难以理解。我是这样理解的,low数组就是该点所属的强联通分量的根节点的访问时间。说起来有点绕嘴,这个需要仔细理解。这样的话,当碰到树枝边时,举例来说,访问到了u,u到v有边,v此时还没有被访问,则边uv是树枝边。当边为树枝边时,low[u] = min(low[u],low[v]),当v被访问过且v仍旧在栈中的时候,边uv为后向边。此时low[u] = min (low[u], dfn[v])。其实这就是一个深搜的过程。如果某个点的low[x] = dfn[x],则x为一个强联通分量的根节点,栈中x以上的点都是以x为根的强联通分量中的点。

所谓缩点,就是把一个强联通分量作为一个点。

该题代码:

#include <iostream>
#include <cstdio>
#include <string.h>
#include <vector>
#include <queue>
#include <stack>
using namespace std;

#define CLR(arr,val) memset(arr,val,sizeof(arr))
const int N = 110;
int vis[N],head[N],low[N],dfn[N],numQ[N],indegree[N],outdegree[N];
int ss[N],cnts = 0;//stack
int numnode,numedge,timevis,numcnt;
struct edge{
	int x,y,next;
}ee[N*N];
void add_edge(int lp,int rp){
	ee[numedge].x = lp;
	ee[numedge].y = rp;
	ee[numedge].next = head[lp];
	head[lp] = numedge++;
}
int min(int x,int y){
	return x < y ? x : y;
}
void targin(int x){
	dfn[x] = low[x] = ++timevis;
	vis[x] = 1;
	cnts++;
	ss[cnts] = x;
	for(int i = head[x]; i != -1; i = ee[i].next){
		int rp = ee[i].y;
		if(!vis[rp]){
		  targin(rp);
		  low[x] = min(low[x],low[rp]);
		}
		else if(vis[rp] == 1){
		  low[x] = min(low[x],dfn[rp]);
		}
	}
	if(dfn[x] == low[x]){
		//printf("x == %d\n",x);
	  numcnt++;
	  while(1){
        int tt = ss[cnts--];
		//printf("tt = %d\n",tt);
		//printf("numcnt = %d\n",numcnt);
		vis[tt] = 2;
		numQ[tt] = numcnt;
		if(tt == x)break;
	  }
	}
}
int main(){
	//freopen("1.txt","r",stdin);
	while(scanf("%d",&numnode) != EOF){
		CLR(head,-1);
		CLR(dfn,0);
		CLR(low,0);
		numedge = 0;
		timevis = 0;
		numcnt = 0;
		cnts = 0;
		CLR(ss,0);
		for(int i = 1; i <= numnode;++i){
		  int x;
		  while(scanf("%d",&x)&&x){
		    add_edge(i,x);
		  }
		}
		for(int i = 1; i <= numnode; ++i){
		   if(!dfn[i])
			   targin(i);
		}
		//printf("numcnt = %d\n",numcnt);
		if(numcnt == 1){
		  printf("1\n0\n");
		}
		else{
			for(int i = 0; i < numedge; ++i){
				int lp = ee[i].x;
				int rp = ee[i].y;
				if(numQ[lp] != numQ[rp]){
					//printf("%d %d\n",numQ[lp],numQ[rp]);
				  outdegree[numQ[lp]]++;
				  indegree[numQ[rp]]++;
				}
			}
			int ans1 = 0,ans2 = 0;
			for(int i = 1; i <= numcnt; ++i){
			   if(indegree[i] == 0)
				   ans1++;
			   if(outdegree[i] == 0)
				   ans2++;
			}
			printf("%d\n",ans1);
			printf("%d\n",max(ans1,ans2));
		}
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值