POJ 2942 Knights of the Round Table (奇圈+点双联通)

题意:在亚瑟王的圆桌骑士团中,某些骑士两两之间相互憎恨,这样一来他们开会的时候边不能相邻的坐着。即肯定存在某些人不能参加会议。假如一个骑士所有的会议都不能出席,那么他就会被驱逐。现在已知那些骑士之间相互憎恨,求最少要驱逐多少名骑士。(开会时人数必需>=3且为奇数)
题解:建图时,对互相不憎恨的两人之间连一条边。对任意一名骑士来说,他要能出席某次会议则他左右的人都不能与他互相憎恨。将每次参加会议的所有人(不一定是整个骑士团,只需人数>=3且为奇数)看做一个点双联通分量,那么每个点都至少有两个点与他相邻。即需要保证双联通分量中存在奇圈。因为只要点双连通分量中存在奇圈,那么这个分量中所有的点都可以出现在奇圈上。求奇圈时可以用到这样一个性质,一个图是二分图当且仅当图中不存在奇圈。那么我们只需判断一个图是否是二分图就可以判断此图存在奇圈,可以用交替染色。

#include <iostream>
using namespace std;

#define N 1005
#define min(a,b) (a<b?a:b)

int n, m;
int size, id, scc;
int color[N], head[N];
int low[N], dfn[N], block[N];
int stack[N], temp[N], top, cnt; // temp 是一个临时的栈,存储点双连通分支
bool map[N][N], expell[N], instack[N];  // expell[i]==true表示i需要被驱逐
struct { int v, next; } edge[N*1000];

bool odd_cycle ( int u, int clr ) //判断是否存在奇圈
{
	color[u] = clr;
	for ( int i = head[u]; i; i = edge[i].next )
	{
		int v = edge[i].v;
		if ( block[v] == scc )
		{
			if ( color[v] && color[v] == color[u] ) 
				return true;
			if ( !color[v] && odd_cycle (v, -clr) )
				return true;
		}
	}
	return false;
}
	
void Tarjan ( int u, int father )
{
	int v, t;
	stack[++top] = u;
	instack[u] = true;
	dfn[u] = low[u] = ++id;
	for ( int i = head[u]; i; i = edge[i].next )
	{
		v = edge[i].v;
		if ( v == father ) continue;
		if ( ! dfn[v] )
		{
			Tarjan ( v, u );
			low[u] = min ( low[u], low[v] );
			if ( low[v] >= dfn[u] )  // u是割点
			{
				scc++;
				do {
					t = stack[top--];
					instack[t] = false;
					block[t] = scc;
					temp[++cnt] = t; //出栈的同时把所有的点记录在temp中,即用temp来储存双连通分支内所有的点
				} while ( t != v ); //注意不要把u出栈,因为一个割点可能属于不同的双联通分支
				
				temp[++cnt] = u; // 割点u属于不同的双联通分支,所以它必然也属于temp
				memset(color,0,sizeof(color)); // 所有颜色置为0
			    if ( cnt >= 3 && odd_cycle(u,1) ) // 当temp中存在奇圈,那么temp中的所有人都可以留下
				{
					while ( cnt != 0 )
						expell[temp[cnt--]] = false;
				}
				else cnt = 0; 
			}
		}
		else if ( instack[v] )
			low[u] = min ( low[u], dfn[v] );
	}
}


void initial()   
{
	top = cnt = 0;
	size = id = scc = 0;
	memset(map,0,sizeof(map));
	for ( int i = 1; i <= n; i++ )
	{
		expell[i] = true; 
		instack[i] = false;
		dfn[i] = low[i] = block[i] = head[i] = 0;
	}
}

void add ( int u, int v )
{
	size++;
	edge[size].v = v;
	edge[size].next = head[u];
	head[u] = size;
}

int main()
{
	int u, v, i, j;
	while ( scanf("%d%d",&n,&m) && (m+n) )
	{
		initial();
		while ( m-- )
		{
			scanf("%d%d",&u,&v);
			map[u][v] = map[v][u] = true;
		}

		for ( i = 1; i <= n; i++ )
			for ( j = i+1; j <= n; j++ )
				if ( ! map[i][j] )
					add(i,j), add(j,i);
		
		for ( i = 1; i <= n; i++ )
			if ( ! dfn[i] ) Tarjan(i,-1);

		int res = 0;
		for ( i = 1; i <= n; i++ )
		    if ( expell[i] ) res++;

		printf("%d\n", res );
	}
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值