hdu 3836,tarjan算法的应用(有向图缩点)

// hdu Equivalent Sets.cpp : 定义控制台应用程序的入口点。
//
/*
题目描述:将题目中的集合转换为顶点,A集合是B集合的子集,转换为一条有向边<A,B>,即题目给我们一个有向图,问最少需要添加多少条边使之成为强连通图。
解题思路:通过tarjan算法找出图中的所有强连通分支,并将每一个强连通分支缩成一个点(因为强连通分量本身已经满足两两互相可达)。
要使缩点后的图成为强连通图,每个顶点最少要有一个入度和一个出度,一条边又提供一个出度和一个入度。
所以可以通过统计没有入度的顶点数 noInDegree 和 没有出度的顶点数 noOutDegree。
所需要添加的边数就是noInDegree和noOutDegree中的最大值。
*/
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <stack>
using namespace std;

const int MAXN = 20000+10;

vector<int>grap[MAXN]; //稀疏图,用邻接表表示图
stack<int>S;

int low[MAXN]; //low[u] 为u或u的子树能够追溯到的最早的栈中节点的次序编号
int num[MAXN]; //num[u] 为u搜索的次序编号(时间戳)
int visited[MAXN];  //标记是否已经被搜索过
int instack[MAXN]; //标记是否在栈中
int index; //顶点的前序编号
int cnt_scc;   //记录总共将图缩成多少个点
int belong[MAXN];  //belong[i] = j 表示原图的点i缩点后为点j

int min(int a, int b)
{
	return a < b ? a : b;
}

int max(int a, int b)
{
	return a > b ? a : b;
}

//初始化
void init(int n)
{
	for(int i=0; i<=n; i++)
	{
		grap[i].clear();
	}
	while(!S.empty())
	{
		S.pop();
	}
	memset(instack,0,sizeof(instack));
	memset(visited,0,sizeof(visited));
	memset(low,-1,sizeof(low));
	memset(num,-1,sizeof(num));
	memset(belong,-1,sizeof(belong));
	index = 0;
	cnt_scc = 0;
}

//找出连通分支,并缩点
void tarjan(int v)
{
	low[v] = num[v] = ++index;
	S.push(v);
	instack[v] = 1;
	visited[v] = 1;

	for(int i=0; i<grap[v].size(); i++)
	{
		int w = grap[v][i];
		if(!visited[w])
		{
			tarjan(w);
			low[v] = min(low[v],low[w]);  //v或v的子树能够追溯到的最早的栈中节点的次序编号
		}
		else if(instack[w])  //(v,w)为后向边
		{
			low[v] = min(low[v],num[w]);
		}
	}
	int u;
	if(low[v] == num[v])  //满足强连通分支条件,进行缩点
	{
		++cnt_scc;
		do
		{
			u = S.top();
			belong[u] = cnt_scc;  //缩点
			S.pop();
			instack[u] = 0;    //出栈解除标记
		}while(u != v);
	}
}


int main()
{
	int n,m;
	int a,b;
	int indegree[MAXN];
	int outdegree[MAXN];
	while(cin>>n>>m)
	{
		init(n);
		memset(indegree,0,sizeof(indegree));
		memset(outdegree,0,sizeof(outdegree));
		while(m--)
		{
			cin>>a>>b;
			grap[a].push_back(b);
		}
		for(int i=1; i<=n; i++)
		{
			if(!visited[i])
			{
				tarjan(i);
			}
		}
		//求缩点后,各个顶点的出度和入度
		for(int i=1; i<=n; i++)
		{
			for(int j=0; j<grap[i].size(); j++)
			{
				int k = grap[i][j];
				if(belong[i] != belong[k])
				{
					indegree[belong[k]]++;
					outdegree[belong[i]]++;
				}
			}
		}

		a = b = 0;
		for(int i=1; i<=cnt_scc; i++)
		{
			if(!indegree[i])
			{
				a++;
			}
			if(!outdegree[i])
			{
				b++;
			}
		}
		if(cnt_scc == 1) //如果图已经为强连通图
		{
			cout<<"0"<<endl;
		}
		else
		{
			cout<<max(a,b)<<endl;
		}
	}
	return 0;
}


 

你可能感兴趣的:(算法)