POJ2942 Knights of the Round Table(双联通分量+奇圈判断)

题目大意:有n个骑士要开会,有m对仇恨关系,可以坐很多张圆桌,每张圆桌至少三个人。求有多少个骑士不能参加会议,满足以下条件:1、相互憎恨的骑士不能相邻 2、出席会议的骑士必须是奇数


思路:既然给出来的仇恨关系,那么我们可以存储反图,如果两个骑士之间有一条连线,那么表示着两个骑士可以坐在一起,那么我们现在要求的就是双联通分量。


这里简单介绍一下双联通分量:

简单来说,无向图G如果是双连通的,那么至少要删除图G的2个结点才能使得图G不连通。换而言之,就是图G任意2个结点之间都存在两条以上的路径连接(注意:路径不是指直接相连的边),那么双连通分量就是指无向图G的子图G’是双连通了。


判断这个环是否为奇数环,可以采用交叉染色的方法。

#include<cstdio>
#include<cstring>
#include<stack>
#include<vector>
#define Min(a,b) a<b?a:b
#define MAXN 1010
using namespace std;
struct E
{
	int v;
	int next;
}edge[2000010];
int cnt;
int head[MAXN];
void add_edge(int u,int v)
{
	edge[cnt].v = v;
	edge[cnt].next = head[u];
	head[u] = cnt++;
}
struct T
{
	int u,v;
	T(){}
	T(int _u,int _v)
	{
		u = _u;
		v = _v;
	}
};
stack<T> mystack;
int arr[MAXN][MAXN];
int n,m;
int dcnt,low[MAXN],dfn[MAXN];
bool mark[MAXN],able[MAXN];
int vis[MAXN];
bool get_color(int u)//染色法判断
{
	for(int i = head[u]; i != -1; i = edge[i].next)
	{
		int v = edge[i].v;
		if(mark[v])
		{
			if(vis[v] == -1)
			{
				vis[v] = (vis[u]+1)%2;
				if(get_color(v)) return true;
			}
			else if(vis[u] == vis[v]) return true;
		}
	}
	return false;
}
void check(int u)//判断该环是否为奇数
{
	memset(vis,-1,sizeof(vis));
	vis[u] = 0;
	if(get_color(u))
	{
		for(int i = 1; i <= n; i++)
			if(mark[i]) able[i] = 1;
	}
}
void dfs(int u,int fa)
{
	low[u] = dfn[u] = ++dcnt;
	for(int i = head[u]; i != -1; i = edge[i].next)
	{
		int v = edge[i].v;
		if(!dfn[v])
		{
			mystack.push(T(u,v));
			dfs(v,u);
			low[u] = Min( low[u] , low[v] );
			if(low[v] >= dfn[u])//找到割点,连通块
			{
				memset(mark,0,sizeof(mark));
				int e = 0;
				while(1)
				{
					e++;//统计边的个数
					T x = mystack.top();
					mystack.pop();
					mark[x.u] = mark[x.v] = 1;
					if(u == x.u&&v == x.v) break;
				}
				if(e >= 3)//连通块中至少有三条边,也就是至少有三个人
					check(u);
			}
		}
		else if(dfn[v] < dfn[u])
		{
			mystack.push( T(u,v) );
			low[u] = Min( low[u] , dfn[v] );
		}
	}
}
int main()
{
	while(scanf("%d%d",&n,&m) != EOF&&n&&m)
	{
		memset(head,-1,sizeof(head));
		memset(dfn,0,sizeof(dfn));
		memset(low,0,sizeof(low));
		memset(able,0,sizeof(able));
		memset(mark,0,sizeof(mark));
		memset(edge,0,sizeof(edge));
		memset(arr,0,sizeof(arr));
		while(!mystack.empty()) mystack.pop();
		cnt = 0;
		dcnt = 0;
		for(int i = 1; i <= m; i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			arr[a][b] = 1;
			arr[b][a] = 1;
		}
		for(int i = 1; i <= n; i++)//存储反图
			for(int j = i+1; j <= n; j++)
			{
				if(arr[i][j] == 0) 
				{
					add_edge(i,j);
					add_edge(j,i);
				}
			}
		for(int i = 1; i <= n; i++)
		{
			if(!dfn[i])
			{
				dfs(i,-1);
			}
		}
		int sum = 0;
		for(int i = 1; i <= n; i++)//统计能构成圈的骑士
			if(able[i]) sum++;
		printf("%d\n",n-sum);
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值