题意:
亚瑟王要在圆桌上召开骑士会议,为了不引发骑士之间的冲突,并且能够让会议的议题有令人满意的结果,每次开会前都必须对出席会议的骑士有如下要求:
1、 相互憎恨的两个骑士不能坐在直接相邻的2个位置;
2、 出席会议的骑士数必须是奇数,这是为了让投票表决议题时都能有结果。
如果出现有某些骑士无法出席所有会议(例如这个骑士憎恨所有的其他骑士),则亚瑟王为了世界和平会强制把他剔除出骑士团。
现在给定准备去开会的骑士数n,再给出m对憎恨对(表示某2个骑士之间使互相憎恨的),问亚瑟王至少要剔除多少个骑士才能顺利召开会议?
注意:1、所给出的憎恨关系一定是双向的,不存在单向憎恨关系。
2、由于是圆桌会议,则每个出席的骑士身边必定刚好有2个骑士。即每个骑士的座位两边都必定各有一个骑士。
3、一个骑士无法开会,就是说至少有3个骑士才可能开会。题解:
建议没看过Tarjan的先把有关内容看看:
1.有向图中Tarjan求强连通基本原理及流程
2.无向图中Tarjan求图的割点、桥与双连通分量
分析:
题意给出骑士之间不能互相到达的边,可以做出图的补图(即将所有边连上,再把给出的边去掉)。
1.很显然的是,对于一个点,要至少有两条边与之相连,那么这个点才不会被舍弃。
2.对于一个圆桌肯定在原图上是一个环,且该环上的点为奇数个。
3.对于图中的一个环,是图中的一个点双连通子图。
有了上面三点分析,进一步可以发现:
1.一个奇环一定不能进行二匹配染色。同样,不能进行二匹配染色的环一定是奇环。(可用来判断奇环)
证明:每个点都可以决定边另一边的一个点,而一共奇数个点,故起点与终点的颜色一定相同。而偶数环不会出现这种情况。
2.对于一个点双连通子图,若能找到一个奇环,那么该点双连通子图内所有点都在该点双连通子图的某个奇环上。(对于这道题的意义即是找出双连通分量后若一些点能满足则该点双连通子图所有点全能满足)
该结论网上很少给证明,这里给出证明:
如图,一个点双连通分量如果不是单独的一个环,那么肯定两个环有两个交点(不然会出现割点),不妨设为c,d,因为A是奇环,那么A环上c->d的两条路径肯定一奇一偶,那么设环B上有α个点,无论α是奇数还是偶数,都能找到对应路径使得该环成为奇环。得证。
有了上面两条结论,思路就很清晰了。
1.根据求点双连通的方法判断点双连通。
2.求出每个点双连通后立即判断是否可行。
3.统计可行点,ans=n-可行点个数。
(我写的代码常数有点大。。。)
#include<iostream>
#include<cstdio>
#include<stack>
using namespace std;
const int Maxn=1e3+50;
const int Maxm=2e6+50;
typedef pair<int,int> pii;
inline int read()
{
char ch=getchar();int i=0,f=1;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){i=(i<<3)+(i<<1)+ch-'0';ch=getchar();}
return i*f;
}
int n,m,dfn[Maxn],low[Maxn],ind,rt,fg[Maxn],blcnt,cnt,ins[Maxn],col[Maxn];
int g[Maxn][Maxn],before[Maxm],from[Maxm],last[Maxn],to[Maxm],ecnt=1;
pii edge[Maxm];
int st[Maxm],top1,top2;
inline void add(int x,int y)
{
ecnt++;
before[ecnt]=last[x];
last[x]=ecnt;
to[ecnt]=y;
}
inline bool dfs(int now,int c)
{
++cnt;
col[now]=c;
for(int e=last[now];e;e=before[e])
{
int v=to[e];
if(ins[v]!=blcnt)continue;
if(!col[v]){if(dfs(v,-c))return true;}
else if(col[v]==col[now])return true;
}
return false;
}
inline bool judge(int now)
{
cnt=0;
if(dfs(now,1))return true;
return false;
}
inline void Release()
{
blcnt++;
pii t;
do
{
t=edge[top1--];
ins[t.first]=ins[t.second]=blcnt;
col[t.first]=col[t.second]=0;
st[++top2]=t.first;st[++top2]=t.second;
}while(top1);
if(judge(rt))
{
while(top2)
{
fg[st[top2--]]=1;
}
}
top2=0;
}
inline void release(int x,int y)
{
blcnt++;
pii t;
do
{
t=edge[top1--];
ins[t.first]=ins[t.second]=blcnt;
col[t.first]=col[t.second]=0;
st[++top2]=t.first;st[++top2]=t.second;
}while(t.first!=x||t.second!=y);
if(judge(x))
{
while(top2)
{
fg[st[top2--]]=1;
}
}
top2=0;
}
inline void dfs(int now)
{
dfn[now]=low[now]=++ind;
int bz=0;
for(int e=last[now];e;e=before[e])
{
int v=to[e];
if(!dfn[v])
{
++bz;
from[v]=e;
edge[++top1]=make_pair(now,v);
dfs(v);
low[now]=min(low[now],low[v]);
if(rt!=now&&low[v]>=dfn[now]){release(now,v);}
if(rt==now&&bz==2){--bz,release(now,v);}
}
else if(from[now]!=(e^1))
{
low[now]=min(low[now],dfn[v]);
if(dfn[v]<dfn[now])edge[++top1]=make_pair(now,v);
}
}
if(now==rt&&top1)
{
Release();
}
}
int main()
{
while(n=read(),n)
{
m=read();
memset(g,0,sizeof(g));
memset(last,0,sizeof(last));
memset(dfn,0,sizeof(dfn));
memset(from,0,sizeof(from));
memset(ins,0,sizeof(ins));
memset(fg,0,sizeof(fg));
ecnt=1;
for(int i=1;i<=m;i++)
{
int x=read(),y=read();
g[x][y]=g[y][x]=1;
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
if(g[i][j])continue;
add(i,j);add(j,i);
}
int ans=0;
for(int i=1;i<=n;i++){if(!dfn[i])rt=i,dfs(i);}
for(int i=1;i<=n;i++){if(fg[i])++ans;}
printf("%d\n",n-ans);
}
}