Trajan算法(强连通+缩点)

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

问题概述:n所学校,它们通过单向边连接,如果A-->B表示A学校可以传递信息给B学校,那么问题来了,一:至少

要向几个学校传递信息,才能保证所有学校都能收到信息;二:至少要添加多少组关系,才能保证给任意一个学校

原始信息后,其他所有学校都能收到信息,输入第一个数表示有多少学校,后面n行,第i行第k个数表示i-->k(每行

输入到0结束)(POJ1236)

输入样例:                                      对应输出:

5                                                      1

2 4 3 0                                             2

4 5 0

0

0

1 0


两个顶点强连通:有向图G中,两个顶点可以通过某些路径互相到达

强连通图:该有向图中的任意两个顶点都强连通

强连通分量:非强连通有向图中的极大强连通子图(注意不是最大)

定理:

①当一个点既是强连通子图Ⅰ中的点,又是强连通子图Ⅱ中的点,则它是强连通子图Ⅰ∪Ⅱ中的点

②强连通分量一定由若干个环组成的

③在任何深度优先搜索中,同一强连通分量内的所有顶点均在同一棵深度优先搜索树中,也就是说,强连通分量一

定是有向图的某个深搜树子树


Trajan主要原理:

time[k]:第一次遍历到k点的时

low[k]:k点所在强连通分量子图中第一个被搜到的点的time值

vis[k]:k点是否已经遍历

栈:每当搜索到一个点时,将它压入栈,当这个点k所有子树全部搜索完毕时,如果low[k]==time[k],说明已经找到

了一个强连通分量,将k以及在k之上的元素弹全部出栈(它们全部属于一个强连通分量,且这个强连通分量不含其

它点)


搜索过程:

→当点k有与点c相连,如果此时(time[k]时)c不在栈中,搜索c点,当c点及其子树搜索完毕回溯后,计算出

low[k] min(low[k], low[c])

→当点k有与点c相连,如果此时(time[k]时)c在栈中,low[k] = min(low[k], time[c])

→搜索完毕后,栈内应该没有点了

期间保证:每个点每条边都只被搜索1次,且必须搜索1次,复杂度n+m,如果遍历完整个搜索树后某个点的time值

等于low值,则它是该搜索子树的根,这时,它以上(包括它自己)一直到栈顶的所有元素组成一个强连通分量,但

属于该强连通分量的所有的点low值不一定一致(但只有根节点满足low值与time值相等)


缩点过程:

→本质:将一个联通块作为一个点ltt[k]:k点属于第ltt[k]个联通块

→遍历一遍所有的边,如果边(u,v)中u和v属于不同联通块,则将它们两个所在的联通块连接在一起


题解:

设ain为缩点之后入度为0的点的个数,aout为缩点之后出度为0的点的个数,显然:这题第一个答案就是ain,第二

个答案当全图强连通时答案为0,否则为max(ain, aout)


#include<stdio.h>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;
vector<int> v[105], nv[105];
stack<int> st;
int n, t, vis[105], inst[105], low[105], time[105], ltt[105], num, ain, aout, in[105], out[105];
void Trajan(int x);
int main(void)
{
	int i, j, x, temp;
	scanf("%d", &n);
	for(i=1;i<=n;i++)
	{
		while(scanf("%d", &x), x!=0)
			v[i].push_back(x);
	}
	t = num = 0;
	for(i=1;i<=n;i++)
	{
		if(vis[i]==0)
			Trajan(i);
	}
	for(i=1;i<=n;i++)
	{
		for(j=0;j<v[i].size();j++)
		{
			temp = v[i][j];
			if(ltt[i]!=ltt[temp])
				nv[ltt[i]].push_back(ltt[temp]);
		}
	}
	for(i=1;i<=num;i++)
	{
		for(j=0;j<nv[i].size();j++)
			in[nv[i][j]]++, out[i]++;
	}
	ain = aout = 0;
	for(i=1;i<=num;i++)
	{
		if(in[i]==0)
			ain++;
		if(out[i]==0)
			aout++;
	}
	printf("%d\n", ain);
	if(num==1)
		printf("0\n");
	else
		printf("%d\n", max(ain, aout));
	return 0;
}

void Trajan(int x)
{
	int i, temp;
	st.push(x);
	inst[x] = vis[x] = 1;
	low[x] = time[x] = ++t;
	for(i=0;i<v[x].size();i++)
	{
		temp = v[x][i];
		if(vis[temp]==0)
		{
			Trajan(temp);
			low[x] = min(low[x], low[temp]);
		}
		else if(inst[temp]==1)
			low[x] = min(low[x], time[temp]);
	}
	if(low[x]==time[x])
	{
		num++;
		while(st.empty()==0)
		{
			temp = st.top();
			st.pop();
			ltt[temp] = num;
			inst[temp] = 0;
			if(temp==x)
				break;
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值