1. 程式人生 > 實用技巧 >[SP2878]KNIGHTS - Knights of the Round Table

[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;
}