[SP2878]KNIGHTS - Knights of the Round Table

[SP2878]KNIGHTS - Knights of the Round Table

一.前言

​ 脑阔完全转不动……都不知道要干些什么,题目链接

二.思路

​ 首先它是圆桌骑士开会,所以他们开会的样子是一个环,并且避免平票需要只有奇数个人(不能一人开会)。将仇恨关系建图不好做,于是建立补图,将可以坐在一起的骑士连边,这样形成了一个可能的座位形状?然后去里面找尽可能多的奇环。输出不被任何一个奇环包括的骑士。

​ 然后到这里我们会有一个结论如下:

对于一个点双连通分量,里面若是有一个奇环,那么该点双连通分量里面所有的点都可以被包括在奇环之中

虽然我也不知道为什么会出现这个结论(好像写边双也可)就象征性的推导一下:

首先点双连通分量由一些环组成

假设在点双连通分量之中有环 a ,若 a 是奇环,那么 a 中所有点都可以开会,不影响答案

若 a 是偶环,那么将它和点双中的奇环链接成一个大奇环就可以了。

我也不知道是不是对的hhh,感性理解一下……同样我也不好解释奇环和点双的关系,硬要说的话,就是 tarjan 缩点之后不会有环,换而言之所有的环都在点双连通分量之中,由于这个结论很方便,就这么写了

​ 现在需要判断一个点双里面有没有奇环。对于一个没有奇环的图来说,他一定是一个二分图(有了奇环就构不成二分图),转化为判断一个点双是不是二分图,使用二分图黑白染色,即将一个点周围的点全部染上相异的颜色,有冲突就不是二分图,也就是有奇环。

​ 这样写就可以了。

三.CODE

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<fstream>
#include<cmath>
#include<cstring>
using namespace std;
int read(){
	char ch=getchar();
	int res=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())res=res*10+(ch-'0');
	return res*f;
}
const int MAXN=1e6+5;
int n,m;
bool hate[1005][1005];
int head[1005],ne[MAXN],to[MAXN],tot;
void add(int x,int y){
	to[++tot]=y,ne[tot]=head[x],head[x]=tot;
}
void build(){
	for(int i=1;i<=n;++i)
		for(int j=i+1;j<=n;++j)
			if(!hate[i][j])add(i,j),add(j,i);
}
int dfn[1005],low[1005],date;
int st[1005],top,rs[1005];
bool vis[1005],fl,wdnmd[1005];
int ans;
bool check(int x,int fa){
	if(fl)return 1;
	for(int i=head[x];i;i=ne[i]){
		int v=to[i];
		if(v==fa||!vis[v])continue;
		if(rs[v]==-1){
			rs[v]=rs[x]^1;//染色
			check(v,x);
			if(fl)return 1;
		}
		else if(rs[v]!=(rs[x]^1))return (fl=1);//冲突
	}
	return 0;
}
void dfs(int x,int fa){//tarjan
	dfn[x]=low[x]=++date;
	st[++top]=x;
	for(int i=head[x];i;i=ne[i]){
		int v=to[i];
		if(!dfn[v]){
			dfs(v,x);
			low[x]=min(low[x],low[v]);
			if(low[v]>=dfn[x]){//出现点双
				int count=1;
				fl=0;
				memset(vis,0,sizeof(vis));
				memset(rs,-1,sizeof(rs));
				vis[x]=1;//将当前点双之中的全部打上标记
				rs[x]=0;
				while(st[top]!=v)vis[st[top--]]=1,count++;
				vis[v]=1,top--,count++;
				if(count>=3&&check(x,x))for(int i=1;i<=n;++i)if(vis[i])wdnmd[i]=1;
                //二分图染色
			}
		}
		else low[x]=min(low[x],dfn[v]);
	}
}
void clean(){
	memset(hate,0,sizeof(hate));
	memset(head,0,sizeof(head));
	tot=0;ans=0;
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(wdnmd,0,sizeof(wdnmd));
}
int main(){
	while(1){
		n=read();m=read();
		if(n==m&&n==0)break;
		clean();//多测清空
		for(int i=1,x,y;i<=m;++i){
			x=read();y=read();
			hate[x][y]=hate[y][x]=1;
		}
		build();//建立补图
		for(int i=1;i<=n;++i)if(!dfn[i]){//可能会不连通,找点双
			memset(dfn,0,sizeof(dfn));
			memset(low,0,sizeof(low));
			date=top=0;
			dfs(i,i);
		}
		for(int i=1;i<=n;++i)ans+=(wdnmd[i]==0);//加上不被点双标记的
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2020-08-19 19:20  clockwhite  阅读(158)  评论(0编辑  收藏  举报