poj 2942 圆桌武士 双连通分量(BCC)+二分图+奇圈判断

//http://blog.csdn.net/lyy289065406/article/details/6756821
//刘汝佳版
/*
大致题意:
亚瑟王要在圆桌上召开骑士会议,为了不引发骑士之间的冲突,并且能够让会议的议题有令人满意的结果,每次开会前都必须对出席会议的骑士有如下要求:
1、  相互憎恨的两个骑士不能坐在直接相邻的2个位置;
2、  出席会议的骑士数必须是奇数,这是为了让投票表决议题时都能有结果。
 
如果出现有某些骑士无法出席所有会议(例如这个骑士憎恨所有的其他骑士),则亚瑟王为了世界和平会强制把他剔除出骑士团。
       现在给定准备去开会的骑士数n,再给出m对憎恨对(表示某2个骑士之间使互相憎恨的),问亚瑟王至少要剔除多少个骑士才能顺利召开会议?
 
注意:1、所给出的憎恨关系一定是双向的,不存在单向憎恨关系。
2、由于是圆桌会议,则每个出席的骑士身边必定刚好有2个骑士。即每个骑士的座位两边都必定各有一个骑士。
3、一个骑士无法开会,就是说至少有3个骑士才可能开会。
 
解题思路:
综合性非常强的图论题
 
1、补图 
2、双连通分量
3、二分图 
4、奇圈
5、判定一个图是否为二分图的方法:交叉染色法
6、Tarjan算法

1、 补图
图G的补图~G就是把图G原有的边全部删去,原本不存在的边全部连上。
 
2、双连通分量
       简单来说,无向图G如果是双连通的,那么至少要删除图G的2个结点才能使得图G不连通。换而言之,就是图G任意2个结点之间都存在两条以上的路径连接(注意:路径不是指直接相连的边),那么双连通分量就是指无向图G的子图G’是双连通了。
 
3、二分图
       二分图又叫二部图,这个百度百科有定义,了解一下二分图是什么样子的可以了,无需深入去了解。不懂得同学等到做二分图的题目时再认真学吧。
 
4、 奇圈
用一条线把奇数个点串连起来,所得到的闭合的圈就是奇圈了。其实奇圈就是有奇数个顶点的环。
 
5、交叉染色法判定二分图
       初始化所有结点为无色(颜色0)状态,用DFS遍历一个图G的同时,顺便对结点染色(只染1、2色),注意遍历过的结点还可以再遍历重新上色。让遍历到某个时候在对结点t染色时,发现边s->t的另一个结点s已染色,且s的颜色与当前正在对t染的颜色相同,那么图G必定不是二分图。
       这是因为想象一下二分图就像是河的两岸有两排结点,每染色一次则过河一次,那么相同颜色的结点必定在同一侧。一旦出现异侧有相同颜色的结点,就可以说明图G不是二分图了。
 
6、Tarjan算法
       我希望大家主要去学习一下这个算法的基本原理,尤其是DFN数组和Low数组,还有什么是深搜树,什么是树枝边,什么是后向边。
学习一下Tarjan算法求割点的过程(注意我上文是建议大家不要用Tarjan算法去求解割点的题,但不是让大家不要看它求割点的过程),因为这个过程是求双连通分量的关键。

两条重要定理:
	若某块不可染色为二分图,则该块存在奇圈;
	若某块存在奇圈,那么该块中的所有点都存在与奇圈中;
*/
#include<stdio.h>
#include<string.h>
#include<vector>
#include<stack>
using namespace std;
const int maxn = 1005;
const int inf = 1<<30;
int n,m;
int time,bcc_cnt;
int low[maxn],dfn[maxn],iscut[maxn];
int bccno[maxn],ood[maxn];				//bccno记录每个点所属的联通分量 
bool maps[maxn][maxn];
int color[maxn];						//判断二分图
vector<int>map[maxn],bcc[maxn];			//bcc存联通分量
struct edge
{
	int u,v;
	edge( int x,int y )
	{
		u = x; v = y;
	}
};
stack<edge>S;

bool bipartite( int u,int b )  //起点
{
	for( int i = 0; i < map[u].size(); i ++ )
	{
		int v = map[u][i];
		if( bccno[v] != b )
			continue;
		if( color[v] == color[u] )         
			return false;
		if( !color[v] )             
		{
			color[v] = 3 - color[u];
			if( !bipartite(v,b) )
				return false;
		}
	}
	return true;
}

void tarjan( int u, int fa )
{
	int child = 0;              //平行边
    low[u] = dfn[u] = ++time;
    for( int i = 0; i < map[u].size(); i ++ )
    {
        int v = map[u][i];
        if( !dfn[v] )
        {
			child ++;
			S.push( edge(u,v) );						//把遍历的每个边压入栈
            tarjan( v,u );
            low[u] = low[u] <= low[v] ? low[u]:low[v];   //用后代low更新自己
			if( low[v] >= dfn[u] )    
			{
				iscut[u] = true;					//割点
				bcc_cnt ++; bcc[bcc_cnt].clear();
				while(1)							//缩点
				{
					edge x = S.top(); S.pop();
					if( bccno[x.u] != bcc_cnt )
					{
						bcc[bcc_cnt].push_back(x.u); bccno[x.u] = bcc_cnt;
					}
					if( bccno[x.v] != bcc_cnt )
					{
						bcc[bcc_cnt].push_back(x.v); bccno[x.v] = bcc_cnt;
					}
					if( x.u == u && x.v == v )
						break;
				}
			}
        }
        else if( dfn[v] < dfn[u] && v != fa )     //用反向边更新自己
		{
			S.push( edge(u,v) );				  //把遍历的每个边压入栈
            low[u] = low[u] <= dfn[v] ? low[u]:dfn[v];
		}
    }
	if( fa < 0 && child == 1 )						//根节点
		iscut[u] = 0;
}

void find_bcc()
{
	time = bcc_cnt = 0;
	memset( low,0,sizeof(low) );
    memset( dfn,0,sizeof(dfn) );
	memset( bccno,0,sizeof(bccno) );
	memset( iscut,0,sizeof(iscut) );
	for( int i = 1; i <= n; i ++ )
	{
		if( !dfn[i] )
			tarjan( i,-1 );
	}
}

void init()                                        //初始化
{
    for( int i = 1; i <= n; i ++ )
    {
            map[i].clear();
			bcc[i].clear();
    }
	while( !S.empty() )	S.pop();
    memset( maps,0,sizeof(maps) );
}
int main()
{
	//freopen( "data.in","r",stdin );
    int u,v,d;
    while( scanf("%d%d",&n,&m) == 2 , n )
    {
         init();
         for( int i = 1; i <= m; i ++ )
         {
             scanf("%d%d",&u,&v);
			 maps[u][v] = maps[v][u] = true;
		 }
		 for( int i = 1; i <= n; i ++ )          //建补图
		 {
			 for( int j = i+1; j <= n; j ++ )
			 {
				 if( !maps[i][j] )
				 {
					 map[i].push_back(j);
					 map[j].push_back(i);
				 }
			 }
		 }
         find_bcc();							//双联通分量

		 memset( ood,0,sizeof(ood) );
		 for( int i =1; i <= bcc_cnt; i ++ )
		 {
			 memset( color,0,sizeof(color) );
			 for( int j = 0; j < bcc[i].size(); j ++ )	//把每个联通分量中的点标记出来
				 bccno[bcc[i][j]] = i;
			 int u = bcc[i][0];
			 color[u] = 1;
			 if( !bipartite(u,i) )						//在第i个联通分量中判断是否为二分图
			 {
				 for( int j = 0; j < bcc[i].size(); j ++ )
					 ood[bcc[i][j]] = 1;
			 }
		 }
		 int ans = n;
		 for( int i = 1; i <= n; i ++ )
			 if( ood[i] )
				 ans --;
		 printf("%d\n",ans);
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值