Equivalent Sets(HDU 3836)---加边成强连通分量

题目链接

题目描述

To prove two sets A and B are equivalent, we can first prove A is a subset of B, and then prove B is a subset of A, so finally we got that these two sets are equivalent.
You are to prove N sets are equivalent, using the method above: in each step you can prove a set X is a subset of another set Y, and there are also some sets that are already proven to be subsets of some other sets.
Now you want to know the minimum steps needed to get the problem proved.

输入格式

The input file contains multiple test cases, in each case, the first line contains two integers N <= 20000 and M <= 50000.
Next M lines, each line contains two integers X, Y, means set X in a subset of set Y.

输出格式

For each case, output a single integer: the minimum steps needed.

输入样例

4 0
3 2
1 2
1 3

输出样例

4
2

分析

题目大意是给定一个有向图,求最少要加几条边可以使得这个图变成强连通图。
考虑到图中可能有环,故要对这个有向图进行缩点得到一个DAG图(有向无环图)。对于求得的DAG图,分别统计入度和出度为0的点的个数,而要加的边数取决于入度为0的个数和出度为0的个数的最大值----①。
原理是这样的:首先我们可以确定,为了满足强连通分量中各点的出入度不为0,那么至少要有①条才。而对于缩点之后的图实际上是由多条链组成的,对于每一条链,我们将首尾连在一起就能构成一个新的环,这样一个带环的新图又可以缩点,这样往复缩点、加边,这样只用①次就可以将整个图缩成一个点即强连通图,见图:
Equivalent Sets(HDU 3836)---加边成强连通分量_第1张图片
Equivalent Sets(HDU 3836)---加边成强连通分量_第2张图片

源程序

tarjan算法

#include 
#define MAXN 20005
#define MAXM 50005
using namespace std;
struct Edge{
	int v,next;
	Edge(){};
	Edge(int _v,int _next){
		v=_v,next=_next;
	}
}edge[MAXM];
stack<int> s;
int EdgeCount,head[MAXN];
int t,n,m,cnt,color;
int dfn[MAXN],low[MAXN],num[MAXN]; 
bool vis[MAXN],in[MAXN],out[MAXN];
void addEdge(int u,int v)
{
	edge[++EdgeCount]=Edge(v,head[u]);
	head[u]=EdgeCount;
}
void init()
{
	for(int i=1;i<=n;i++){
		head[i]=dfn[i]=low[i]=num[i]=0;
		vis[i]=in[i]=out[i]=false;
	}
	EdgeCount=cnt=color=0;
}
void Tarjan(int u)
{
	dfn[u]=low[u]=++cnt;	//时间戳 
	vis[u]=true;	//标记入栈 
	s.push(u);
	for(int i=head[u];i;i=edge[i].next){
		int v=edge[i].v;
		if(!dfn[v]){	//还没被盖上时间戳 
			Tarjan(v);
			low[u]=min(low[u],low[v]);
		} 
		else if(vis[v])	//已盖上时间戳但还在栈中 
			low[u]=min(low[u],dfn[v]); 		//注意这里用在求强连通分量的时候用low[v]更新可能没错 
	}										//但在求割点的时候会漏掉割点
	if(dfn[u]==low[u]){						//因此这里用dfn[v]而不是low[v] 
		color++;
		while(1){
			int tmp=s.top();s.pop();
			vis[tmp]=false;
			num[tmp]=color;		//标记所属连通分量 
			if(tmp==u)break;
		}
	}
}
int main()
{
	while(~scanf("%d%d",&n,&m)){
		init();		//初始化 
		for(int i=1;i<=m;i++){
			int u,v;
			scanf("%d%d",&u,&v);
			addEdge(u,v);
		}
		for(int i=1;i<=n;i++)	//缩点 
			if(!dfn[i])
				Tarjan(i);
		for(int u=1;u<=n;u++){	//统计缩点后的出入度 
			for(int i=head[u];i;i=edge[i].next){
				int v=edge[i].v;
				if(num[u]!=num[v]){
					out[num[u]]=true;
					in[num[v]]=true;
				}
			} 
		}
		int a=0,b=0,ans;
		for(int i=1;i<=color;i++){	//统计出入度为0的个数 
			if(!in[i])a++;
			if(!out[i])b++;
		}
		if(color>1)ans=max(a,b);
		else ans=0;		//恰好为强连通图 
		printf("%d\n",ans); 
	}
}

Kosaraju算法

#include 
#define MAXN 20005
#define MAXM 50005
using namespace std;
struct Edge{
	int v,next;
	Edge(){};
	Edge(int _v,int _next){
		v=_v,next=_next;
	}
};
struct GNode{
	Edge edge[MAXM];
	int EdgeCount,head[MAXN];
	void clear(){
		memset(head,0,sizeof(head));
		EdgeCount=0;
	}
	void addEdge(int u,int v){
		edge[++EdgeCount]=Edge(v,head[u]);
		head[u]=EdgeCount;
	}
};
GNode G,GT;
int t,n,m,cnt,color;
int dfn[MAXN],low[MAXN],num[MAXN];
bool vis[MAXN],in[MAXN],out[MAXN];
void init()
{
	for(int i=1;i<=n;i++){
		dfn[i]=low[i]=num[i]=0;
		in[i]=out[i]=false;
	}
	G.clear();GT.clear();
	cnt=color=0;
}
void dfs1(int u)	//第一次深搜记录搜索完成时间 
{
	vis[u]=true;
	for(int i=G.head[u];i;i=G.edge[i].next){
		int v=G.edge[i].v;
		if(!vis[v]){	//没有访问过 
			dfs1(v); 
		}
	}
	dfn[++cnt]=u;
}
void dfs2(int u)	//第二次深搜记录强连通分量 
{
	vis[u]=true;
	num[u]=color;
	for(int i=GT.head[u];i;i=GT.edge[i].next){
		int v=GT.edge[i].v;
		if(!vis[v])
			dfs2(v);
	}
}
void Kosaraju()
{
	/*第一次深搜*/
	memset(vis,false,sizeof(vis));
	for(int i=1;i<=n;i++)
		if(!vis[i])
			dfs1(i);	
	/*第二次深搜*/ 
	memset(vis,false,sizeof(vis));
	for(int i=n;i>=1;i--){
		if(!vis[dfn[i]]){
			color++;
			dfs2(dfn[i]);
		}
	}
}
int main()
{
	while(~scanf("%d%d",&n,&m)){
		init();
		for(int i=1;i<=m;i++){	//建图 
			int u,v;
			scanf("%d%d",&u,&v);
			G.addEdge(u,v);
			GT.addEdge(v,u);
		}
		Kosaraju();
		for(int u=1;u<=n;u++){	//统计缩点后的出入度 
			for(int i=G.head[u];i;i=G.edge[i].next){
				int v=G.edge[i].v;
				if(num[u]!=num[v]){
					out[num[u]]=true;
					in[num[v]]=true;
				}
			} 
		}
		int a=0,b=0,ans=0; 
		for(int i=1;i<=color;i++){	//统计出入度为0的点个数 
			if(!in[i])a++;
			if(!out[i])b++;
		}
		if(color>1)ans=max(a,b);	//原图非强连通分量 
		printf("%d\n",ans);
	}
}

你可能感兴趣的:(Equivalent Sets(HDU 3836)---加边成强连通分量)