POJ 1236 Network of Schools (Tarjan算法求强连通分量+缩点) 代码详解

传送门:POJ 1236



题目大意

问,对于一个DAG(有向无环图):
1.至少要选几个点,才能从这些点出发到达所有点
2.至少加入几条边,就能从图中任何一个点出发到达所有点


Sample Input

5
2 4 3 0
4 5 0
0
0
1 0

Sample Output

1
2



前置技能

Tarjan算法求强连通分量及缩点。至于Tarjan算法求强连通分量我觉得前人之述备矣,我就不再啰嗦了,一会给出几个不错的网站大家可以参考一下,我的代码中也给出了尽可能详细的注释。


至于缩点,我想再稍微提一下。大家应该知道,一个图求得所有的强连通分量后,这个图就可以看作是由这些所有的强连通分量构成的。现在我们把每个强连通分量看作一个点,由这些点可以构成一个新的图,这就是所谓的缩点。



参考文章Tarjan算法求强连通分量。



思路

先求DAG的强连通分量数,再缩点,可以用tarjan算法来做。
1.:不难想到答案就是缩点之后入度为0的点的个数

2.:设缩点后入度为0的个数是n,出度为0的个数是m,至少添加边的条数就是max(n,m)


以上思路来自POJ 1236题解,至于为什么是这样……我还没想明白,现在只是根据这个题熟悉一下模板,之后思路的可行性分析我会补上的。



注意当整个图为1个强连通分量的时候,缩点是没用的,这时候得不到正确的出度和入度的值,所以要特殊处理一下。


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

//cnt为强连通分量的个数,index为节点访问的编号(每个节点不同)
int cnt,index;
dfn记录节点的被访问时间(时间戳)
//low记录该点或者以这个点为根的子树能够追溯到最早的栈中节点的次序
int dfn[111],low[111];
//vis标记是否在栈中,belong标记节点属于第几个强连通分量
int vis[111],belong[111];
vector<int> map[111];
stack<int> st;

void Tarjan(int u)
{
	int i,v;
	dfn[u]=low[u]=++index;//新点初始化
	st.push(u);  //入栈 
	vis[u]=1;    //标记在栈中 
	for(i=0;i<map[u].size();i++)
	{ //处理与u相邻的节点 
		v=map[u][i];
		if(!dfn[v])
		{ //如果没被访问过,不能用!vis[v],vis表示是否在栈中
			Tarjan(v); //递归处理
			//将强连通分量的所有节点low值改为根节点的dfn值
			if(low[v]<low[u]) low[u]=low[v];
		}
		//如果访问过,并且在栈中,表明存在环
		else if(vis[v]&&dfn[v]<low[u]) low[u]=dfn[v];
	}
	if(dfn[u]==low[u])
	{ //如果是强连通分量子树的根,此时栈内u之上的元素构成起一个强连通分量 
		cnt++; //第cnt个强连通分量 
		while(u!=v)
		{ //让强连通分量的元素出栈并保存它们所属的强连通分量的编号 
			v=st.top();
			st.pop();
			vis[v]=0;
			belong[v]=cnt; //节点v属于第cnt个强连通分量 
		}
	}
}

void init(int n)
{ //初始化函数 
	int i;
	cnt=0;
	index=0;
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(vis,0,sizeof(vis));
	for(i=1;i<=n;i++)
	//如果没被访问过则从该点开始处理,防止有点遗漏 
		if(!dfn[i]) Tarjan(i);
}

int chu[111],ru[111]; //出度和入度

void suodian(int n)
{ //缩点函数,其中n为节点个数
	int i,j,u,v;
	memset(chu,0,sizeof(chu));
	memset(ru,0,sizeof(ru));
	for(i=1;i<=n;i++)
	{ //扫描每个节点 
		u=belong[i]; //原来起点所在的强连通分量
		for(j=0;j<map[i].size();j++)
		{ //每个节点的相邻节点 
			v=belong[map[i][j]]; //原来终点所在的强连通分量 
			if(u!=v)
			{ //如果不在同一强连通分量内
				chu[u]++;
				ru[v]++;
			}
		}		
	}
}

int main()
{
	int i,n,x,ans1,ans2;
	while(~scanf("%d",&n))
	{
		//将栈和vector数组清空 
		for(i=0;i<=n;i++) map[i].clear();
		while(!st.empty()) st.pop();
		for(i=1;i<=n;i++)
			while(1)
			{
				scanf("%d",&x);
				if(x==0) break;
				map[i].push_back(x);
			}
		init(n);
		suodian(n);
		ans1=ans2=0;
		for(i=1;i<=cnt;i++)
		{
			if(ru[i]==0) ans1++;
			if(chu[i]==0) ans2++;
		}
		if(ans1>ans2) ans2=ans1;
		if(cnt==1) printf("1\n0\n"); //如果只有一个强连通分量则特殊处理 
		else printf("%d\n%d\n",ans1,ans2);
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值