POJ2942 点连通分量+奇圈判定 以及强连通分量,边连通,割边,割点

本文深入讲解了Tarjan算法的应用,包括点连通分量、强连通分量及边连通分量的求解,并提供了具体的C语言实现代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:有一群圆桌骑士要开会,会议需要圆桌骑士围着坐成一个圈,相互hate的骑士不能坐在边上,最少需要3个人才能开会,且一桌上面的人数必须是奇数

告诉你相互hate的情况,问最少剔除多少人才能使会议进行?

 

解题思路:通过Tarjan算法找到所有点连通分支,每找到一组,判定是否为奇圈,若是,则将该连通分支中的点标记为可行点。

由于一个点可能会出现在多个连通分支中,所以判断奇圈要再Tarjan算法中进行。

 

Tarjan算法求点连通分量:

定义两个标号 Num[],Low[] 分别表示节点访问的次序号.

Low[v]=Min(Low[w],  

                  Num[v],    

                  Num[w])   存在边v-w且w在栈中

}

Tarjan算法是后根遍历的dfs,并根据标号确定连通性

 

强连通分量:若当前节点Num[v]==Low[v] 则退栈至当前节点,退栈的节点构成一个强连通分量   ,注意强连通分量的判定需要遍历完所有子节点后进行,而点连通分量和边连通分量则是遍历完每个子节点后都要进行。

 

点连通分量:若 Low[w]>=Num[v]  则v为割点,出栈至顶点w,同时将v也归入该连通分量后,继续遍历其他儿子。

边连通分量:若 Low[w]>Num[v],则v->w为桥,出栈至顶点w,归为一个边连通分量,继续遍历其他儿子。

 

#include<stdio.h>
#define Min(a,b) (a<b?a:b)

int N,M;
int Map[1001][1001]; 
int Expelled[1001],Ans;   //判断是否能参加会议,个人 
int InStack[1001];  //是否在栈中
int Stack[1001],Top;         //数组模拟栈 
int Num[1001],Low[1001],Cnt;   //Tarjan算法要用到的标量
int Vertex[1001],vnum; //储存当前求得的连通分量中的点 
int Flag[1001];   

int DFS(int V){   //判断是否是二分图,是返回1,不是返回0 ,交叉染色,遇到颜色相同的则不是二分图 
	int i;
	for(i=0;i<vnum;i++){
		if(Map[V][Vertex[i]]){
			if(!Flag[Vertex[i]]){
				Flag[Vertex[i]]=(Flag[V]==1?2:1);
				return DFS(Vertex[i]);
			}
			else if(Flag[Vertex[i]]==Flag[V])
				return 0;
		}
	} 
	return 1;
}

void Tarjan(int V,int Pre){
	int W,tmp,k,Vtmp,i;
	Stack[++Top]=V;       //按访问顺序进栈 
	InStack[V]=1;
	Num[V]=Low[V]=Cnt++;    //初始化Num,Low指标 
	for(W=1;W<=N;W++){
		if(Map[V][W]&&W!=Pre){   //不访问父亲节点 
			if(!Num[W]){         //算法保证O(V+E)复杂度,访问过的节点不再访问 
				Tarjan(W,V);     
				Low[V]=Min(Low[V],Low[W]);
				if(Low[W]>=Num[V]){      
					vnum=0;
					do{                       //出栈至顶点 W 
						Vtmp=Stack[Top--];
						Vertex[vnum++]=Vtmp;
						InStack[Vtmp]=0;
					}while(Vtmp!=W);
					Vertex[vnum++]=V;     //连通分量包括当前顶点 V 
				//由于一个节点可能存在于多个连通分量中,所以要在这里判定是否有奇圈
					memset(Flag,0,sizeof(Flag));
				 	if(vnum>=3&&!DFS(V)){  //如果不是二分图,则有奇圈 
	 					for(i=0;i<vnum;i++)
	 						Expelled[Vertex[i]]=1;   //标记为可以参加 
	 				}
				}	
			}
			else if(InStack[W])      //在栈中则是反向边 
				Low[V]=Min(Low[V],Num[W]);
		}
	}
}

int main(){
	int i,j,v1,v2;
	while(~scanf("%d %d",&N,&M)){
		Ans=0; 
		memset(Expelled,0,sizeof(Expelled));
		memset(Num,0,sizeof(Num));
		if(N==0&&M==0)
			break;
		for(i=1;i<=N;i++)
			for(j=i+1;j<=N;j++)
				Map[i][j]=Map[j][i]=1;
		for(i=0;i<M;i++){
			scanf("%d %d",&v1,&v2);
			Map[v1][v2]=Map[v2][v1]=0;
		}	
		//构图完成,搜索双连通分量,注意可能有多个不连通的块 
		for(i=1;i<=N;i++){
			if(!Num[i]){ 
				memset(InStack,0,sizeof(InStack));
				Top=0;   //初始化栈顶 
				Cnt=1;   //初始化访问节点标号 
				Tarjan(i,-1);   //-1记录当前访问点的父亲节点 
			} 
		}
		for(i=1;i<=N;i++)
			if(!Expelled[i])
				Ans++;
		printf("%d\n",Ans);
	}	
	return 0;
}


 

POJ2186强连通分量+缩点

#include<stdio.h>
#include<string.h>
#define Min(a,b)  (a>b?b:a)

typedef struct{
	int v,w;
}Edge;

Edge E[50001];
int Head[10001],Next[50001],EdgeNum;
int Num[10001],Low[10001],Cnt;
int InStack[10001],Stack[10001],Top;
int Union[10001],Count;
int Out[10001],Vis[10001],VertexNum[10001];
int Ans;

void AddEdge(int v,int w){
	E[EdgeNum].v=v;
	E[EdgeNum].w=w;
	Next[EdgeNum]=Head[v];
	Head[v]=EdgeNum;
	EdgeNum++;			
}

void Tarjan(int V,int Pre){
	int e,W;
	Num[V]=Low[V]=Cnt++;
	InStack[V]=1;
	Stack[++Top]=V;
	for(e=Head[V];e!=-1;e=Next[e]){
		if(!Num[E[e].w]){
			Tarjan(E[e].w,V);
			Low[V]=Min(Low[V],Low[E[e].w]);
		}
		else if(InStack[E[e].w])
			Low[V]=Min(Low[V],Num[E[e].w]);
	}
	if(Low[V]==Num[V]){
		Count++;
		do{
			W=Stack[Top--];
			InStack[W]=0;
			Union[W]=Count;
		}while(W!=V);
	}
}

int main(){
	int N,M,a,b,i,j,e,AnsV;
	while(~scanf("%d %d",&N,&M)){
		memset(Num,0,sizeof(Num));
		memset(Low,0,sizeof(Low));
		memset(Head,-1,sizeof(Head));
		memset(Next,-1,sizeof(Next));
		EdgeNum=Count=0;   //Count为缩点后顶点数 
		for(i=0;i<M;i++){
			scanf("%d %d",&a,&b);
			AddEdge(a,b);
		}
		for(i=1;i<=N;i++){
			if(!Num[i]){
				Cnt=1;
				Top=0;
				Tarjan(i,-1);
			}
		}
		memset(Out,0,sizeof(Out));  //判断是否有出度 
		memset(Vis,0,sizeof(Vis));  //优化时间 
		memset(VertexNum,0,sizeof(VertexNum));  //计算缩点中点的数量 
		for(i=1;i<=N;i++){ 
			VertexNum[Union[i]]++;      
			if(Vis[Union[i]]) continue;     
			for(e=Head[i];e!=-1;e=Next[e]){
				if(Union[i]!=Union[E[e].w]){       //如果不是一个强连通分支
					Out[Union[i]]=1;   //存在出度 
					Vis[Union[i]]=1;
					break;
				} 
			}
		}
		for(i=1,j=0;i<=Count;i++){
			if(!Out[i]){
				j++;
				AnsV=i;
			}
		} 
		if(j==1)
			printf("%d\n",VertexNum[AnsV]);
		else
			printf("0\n");	
	}
	return 0;
}


 

POJ3352 边连通分量+缩点

#include<stdio.h>
#include<string.h>
#define Min(a,b) (a<b?a:b)

typedef struct{
	int v,w;
}Edge;

Edge E[2002];
int Head[1001],Next[2002],EdgeNum;
int Num[1001],Low[1001],Union[1001],Cnt,Count;
int Stack[1001],InStack[1001],Top;
int N,M;
int Out[1001],Ans;

void AddEdge(int v,int w){
	E[EdgeNum].v=v;
	E[EdgeNum].w=w;
	Next[EdgeNum]=Head[v];
	Head[v]=EdgeNum;
	EdgeNum++;	
}

void Tarjan(int V,int Pre){
	int e,W;
	Num[V]=Low[V]=Cnt++;
	InStack[V]=1;
	Stack[++Top]=V;
	for(e=Head[V];e!=-1;e=Next[e]){
		if(E[e].w==Pre) continue;
		if(!Num[E[e].w]){
			Tarjan(E[e].w,V);
			Low[V]=Min(Low[V],Low[E[e].w]);
			if(Low[E[e].w]>Num[V]){
				Count++;   
				do{
					W=Stack[Top--];
					Union[W]=Count;
					InStack[W]=0; 
				}while(W!=E[e].w); 
			}	
		}
		else if(InStack[E[e].w])
			Low[V]=Min(Low[V],Num[E[e].w]);
	}
}

int main(){
	int i,a,b,e;
	while(~scanf("%d %d",&N,&M)){
		memset(Head,-1,sizeof(Head));
		memset(Next,-1,sizeof(Next));
		memset(InStack,0,sizeof(InStack));
		memset(Union,0,sizeof(Union));
		memset(Out,0,sizeof(Out));
		memset(Num,0,sizeof(Num));
		Ans=EdgeNum=0;
		for(i=0;i<M;i++){
			scanf("%d %d",&a,&b);
			AddEdge(a,b);
			AddEdge(b,a);
		}
		Count=Top=0; //初始化缩点数和栈 
		Cnt=1;
		Tarjan(1,-1);
	 	for(e=0;e<EdgeNum;e++){
	 		if(Union[E[e].v]!=Union[E[e].w]){
		 		Out[Union[E[e].v]]++;
		 		Out[Union[E[e].w]]++;
		 	}
	 	}
		for(i=0;i<=Count;i++)
			if(Out[i]==2) Ans++;
		printf("%d\n",(Ans+1)>>1);
	}
	return 0;
}



                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值