题意:有一群圆桌骑士要开会,会议需要圆桌骑士围着坐成一个圈,相互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;
}