现在的位置: 首页 > 综合 > 正文

poj 2942 Knights of the Round Table(双连通分量+tarjan+二分图判定)

2014年04月05日 ⁄ 综合 ⁄ 共 2420字 ⁄ 字号 评论关闭

http://poj.org/problem?id=2942

题意:

有N个骑士,给出某些骑士之间的仇恨关系,骑士们开会时会围坐在一个圆桌旁。一次会议能够顺利举行,要满足两个条件:

1:任意相互憎恨的两个骑士不能相邻

2:开会人数为大于2的奇数

若某个骑士任何会议都不能参加,那么就必须将他踢出,给出骑士之间的仇恨关系,问最少需要踢出多少个骑士?


思路:

题目要求踢出的人最少,那么其实应该都能尽量坐下来,又不能与仇恨的骑士相邻。而题目给出的是骑士之间的仇恨关系,因此我们首先建立补图,先将给出的仇恨关系以骑士为顶点建立边,然后撤销这些边,将其余可以连的边都连上便是原图的补图。骑士要能围坐在一个圆桌,就是图中顶点能在一个圈中,即在一个双连通分量里,而题目要求的要开会的人数是大于2的奇数那么此题就是求最多有多少个骑士在奇圈中


要求最多有多少骑士在奇圈中,关于奇圈我们要承认这两个定理:

1:若双连通分量中有一个奇圈,则该双连通分量中的所有点都在某个奇圈中

2:若一个双连通分量有奇圈,那么该双连通分量必定不是二分图,他们是充分必要条件。判断是否是二分图可以用交叉染色法,即深度优先搜索染色,如果搜到一个节点的子节点已经染色并且与自己相同,说明不是二分图,那么双连通分量中有奇圈。

这里注意双连通分量里的顶点个数是否是奇数与该双连通分量是否是奇圈无关。


我们可以用tarjan求出每个双连通分量,在其顶点大于2的前提下,对每次求出的双连通分量,根据交叉染色法判断是否有奇圈,如果有,那么这个双连通分量中的点都不用删除(标记一下即可)。

最后,没被标记的肯定不是任何一个奇圈中的顶点。


#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
const int maxn = 1010;
const int maxm = 1000100;
int map[maxn][maxn];
int n,m;
int dfn[maxn],low[maxn],instack[maxn],dep;
int scc,tmp[maxn],block[maxn];
int color[maxn];//给某个双连通深度优先搜索染色
int expell[maxn];//标记顶点是否在某个奇圈中
int cnt;
stack <int> st;
vector <int> edge[maxn];//补图


void init()
{
	for(int i = 1; i <= n; i++)
		edge[i].clear();
	while(!st.empty()) st.pop();
	memset(map,0,sizeof(map));
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(instack,0,sizeof(instack));
	memset(block,0,sizeof(block));
	memset(expell,0,sizeof(expell));
	dep = 0;
	cnt = 0;
	scc = 0;
}
//判断奇圈
bool odd_cycle(int u,int col)
{
	color[u] = col;

	for(int i = 0; i < (int)edge[u].size(); i++)
	{
		int v = edge[u][i];
		if(block[v] == scc)
		{
			if(color[v] && color[v] == color[u])
				return true;
			if(!color[v] && odd_cycle(v,-col))
				return true;
		}
	}
	return false;
}

void tarjan(int u, int fa)
{
	dfn[u] = low[u] = ++dep;
	instack[u] = 1;
	st.push(u);

	for(int i = 0; i < (int)edge[u].size(); i++)
	{
		int v = edge[u][i];
		if(v == fa) continue;
		if(!dfn[v])
		{
			tarjan(v,u);
			low[u] = min(low[u],low[v]);

			if(low[v] >= dfn[u])
			{
				scc++;
				int t;
				do
				{
					t = st.top();
					st.pop();
					instack[t] = 0;
					tmp[++cnt] = t;
					block[t] = scc;
				}while(t != v);//注意不要让u出栈,因为它可能属于多个双连通分量
				tmp[++cnt] = u;//u进临时数组
				memset(color,0,sizeof(color));
				if(cnt >= 3 && odd_cycle(u,1))//若该双连通分量包含顶点个数大于2并且是奇圈时
				{
					while(cnt != 0)
						expell[ tmp[cnt--] ] = 1;//在奇圈内的点全部标记为1
				}
				else cnt = 0;//别忘了将cnt置零
			}
		}
		else if(instack[v])
			low[u] = min(low[u],dfn[v]);
	}
}

int main()
{
	int u,v;
	while(~scanf("%d %d",&n,&m))
	{
		if(n == 0 && m == 0) break;

		init();

		for(int i = 0; i < m; i++)
		{
			scanf("%d %d",&u,&v);
			map[u][v] = map[v][u] = 1;
		}
		
		//求补图
		for(int i = 1; i <= n-1; i++)
		{
			for(int j = i+1; j <= n; j++)
			{
				if(!map[i][j])
				{
					edge[i].push_back(j);
					edge[j].push_back(i);
				}
			}
		}

		for(int i = 1; i <= n; i++)
			if(!dfn[i])
				tarjan(i,-1);


		int res = 0;
		for(int i = 1; i <= n; i++)
			if(expell[i] == 0)
				res++;
		printf("%d\n",res);
	}
	return 0;
}

抱歉!评论已关闭.