憎恨关系也太难处理了,不妨建出反图,$u$到$v$有边则表示他们可以相邻出席会议。那么问题就转化为,求不被任何奇环包含的点的数量。
引理:在同一个奇环中的点必在同一个点双中。
感性证明:假设有两个点$u,v$在同一个奇环中,且在不同的点双中。那么将这$u$所在的点双和这个奇环合并,得到的子图仍是点双联通的,与点双的极大性矛盾。证毕。
那么,我们先跑一遍tarjan,求出所有点双。对每个点双进行二分图判定(染色法),若出现无法保证每条边连接的两个点颜色不同(即推出矛盾),则有这个点双中存在奇环。进一步,这个点双中所有点都被奇环包含(读者自证不难),把它们打上标记。
统计一下没有被标记的点的数量,就是答案。时间复杂度$O(n^2)$.
/**********/省略快读
#define MAXN 1011
ll g[MAXN][MAXN],n,m;//反图
ll dfn[MAXN],low[MAXN],cur=0;
ll s[MAXN],top=0,vcnt=0;
std::vector<ll>vcc[MAXN];//存点双中的点
bool cut[MAXN];
void tarjan(ll u,ll root)//tarjan求点双
{
s[++top]=u;
dfn[u]=low[u]=++cur;
bool flag=0,edge=0;
for(ll v=1;v<=n;++v)
if(g[u][v])
{
edge=1;
if(!dfn[v])
{
tarjan(v,root);
umin(low[u],low[v]);
if(low[v]>=dfn[u])
{
if(flag||u!=root)cut[u]=1;
flag=1;
vcc[++vcnt].push_back(u);
while(s[top]!=v)vcc[vcnt].push_back(s[top--]);
vcc[vcnt].push_back(s[top--]);
}
}
else umin(low[u],dfn[v]);
}
if(!edge)vcc[++vcnt].push_back(u);
}
bool vis[MAXN],invcc[MAXN];
ll c[MAXN];
void cover(ll x)//这次要处理的点双
{
memset(c,0,sizeof c);
memset(invcc,0,sizeof invcc);
for(std::vector<ll>::iterator it=vcc[x].begin();it!=vcc[x].end();++it)invcc[*it]=1;
}
bool dfs(ll u,ll cur)//染色法
{
c[u]=cur;
//("node %lld is colored %lld\n",u,cur);
for(ll v=1;v<=n;++v)
if(invcc[v]&&g[u][v])
{
if(c[v])
{
if(c[v]==c[u])return 1;
}
else if(dfs(v,-cur))return 1;
}
return 0;
}
int main()
{
while(1)
{
n=read(),m=read();
if(!n&&!m)break;
memset(g,0,sizeof g);memset(vis,0,sizeof vis);memset(cut,0,sizeof cut);
memset(dfn,0,sizeof dfn);memset(low,0,sizeof low);
vcnt=0;cur=0;
for(ll i=1;i<=m;++i)
{
ll u=read(),v=read();
g[u][v]=g[v][u]=1;
}
for(ll i=1;i<=n;++i)
for(ll j=1;j<=n;++j)
{
if(i!=j)g[i][j]=!g[i][j];
//if(i>j&&g[i][j])printf("%lld %lld\n",i,j);
}
for(ll i=1;i<=n;++i)
if(!dfn[i])
{
top=0;
tarjan(i,i);
}
/*
for(ll i=1;i<=vcnt;++i)
{
printf("v-DCC %lld:",i);
for(std::vector<ll>::iterator it=vcc[i].begin();it!=vcc[i].end();++it)
printf("%lld ",*it);
putchar('\n');
}
*/
for(ll i=1;i<=vcnt;++i)
{
//printf("v-DCC %lld:\n",i);
cover(i);
if(dfs(vcc[i][0],1))
{
for(ll u=1;u<=n;++u)vis[u]|=invcc[u];
}
vcc[i].clear();
}
ll ans=0;
for(ll i=1;i<=n;++i)
if(!vis[i])++ans;
printf("%lld\n",ans);
}
return 0;
}
为什么不能是边双