POJ 2942
由于@淵立狼 问到,也算是复习下强连通分量和双连通分量。
题意:一些骑士,他们之间有互相讨厌的关系,讨厌的两人不能相邻坐,每次开会都要有奇数个骑士坐在圆桌,永远不能坐进圆桌的骑士将会被剔除,问剔除的骑士有多少个
首先对输入的图建立反图,然后求双连通分量。由于双连通分量如果有奇环的话,他的每一个点都会在奇环里,所以判断双连通是否有奇环,如果有则整个双连通分量的点都不需要剔除。但是由于一个点可能会在两个双连通分量里,所以处理时不能直接加双连通分量的点个数,需要先标记不需要剔除的点,最后一次过统计需要剔除的点的个数。
强连通分量:有向图G中如果任意两点vi,vj间有路径,则G为强连通图。有向图的极大强连通子图叫强联通分量。
算法是Tarjan算法。深度优先搜索过程中如果能回到已经搜索过的点,则说明这个图中含有环路径,而这个环路径中的点会在同一个强连通分量里。所以强连通分量必定是原图的深度优先搜索树的子树,这些子树中的叶子节点跟子树的根节点连通,构成环路。
为了能够求出这些子树,需要两个数组,一个是dfn,标记该节点的深度搜索访问的时间戳,一个是low,标记这个节点以及其子节点所能访问到的节点的最小时间戳。这样当一个节点能够访问到曾经访问过的节点,他就能更新他的low值,并且回退更新他的父节点,一直到low值和dfn值相同。这个low=dfn的节点便是强连通分量的子树的根节点,因为他是这个子树中时间戳最小的节点,且他的子节点都有到他的路径。算法过程中可以使用一个堆栈保存深度搜索所访问过的点,当找到一个强连通分量时,则从栈顶取出元素直到这个强连通分量的子树根节点为止。
算法过程
void Tarjan(int u)
{
low[u]=dfn[u]=tim++;//标记时间戳
vis[u]=true;//标记点已经访问过
sta[top++]=u;//访问过的点入栈
for(int i=head[u];i;i=edg[i].nxt)//遍历每一条边
{
int v=edg[i].to;//u访问的下一个点
if(vis[v])
{
low[u]=min(low[u],dfn[v]);//该点已经被访问过,更新u点的low值
}
else
{
Tarjan(v);//该点未访问过,继续深搜
low[u]=min(low[u],low[v]);//v点能访问到的最小时间戳,u点也能访问到
}
}
if(low[u]==dfn[u])//这一点以及其子树所能访问到的点的最小时间戳是自身,说明构成强连通分量
{
int v;
do
{
v=sta[top-1];//强连通分量的点
top--;
}while(v!=u);
}
}
点双连通分量:无向图如果删掉任意一点,不会对该图的连通性造成影响,则这个图是点双连通图。无向图的极大双连通子图叫双连通分量。
算法与Tarjan算法相似,但是首先要知道割点如何求得。
-
点u在DFS序列中的根节点,如果他有一个以上的孩子,则u是割点。因为两个孩子必须通过u点才能连通。
-
点u在DFS序列中不是根节点,如果他的任何后继v能追溯到的最早祖先节点low[v]>=dfn[u],则u是割点。因为如果u不是割点,则v除了DFS序列所代表的路径外,应该还有一条路径能到达u的祖先,即low[v]<dfn[u]。如果low[v]>dfn[u],则没有第二条路径回到u以及u的祖先;如果low[v]==dfn[u],则v和u都没有第二条路径回到u的祖先。
要求双连通分量,则要求一个点满足low[v]==dfn[u],说明这个子树的每个点存在除了DFS序列外另一条路径连通u,但是不能到回u的祖先节点。
算法过程:
void Tarjan(int u,int pre)
{
dfn[u]=low[u]=tim++;
vis[u]=true;
sta[top++]=make_pair(pre,u);//u的前继边进堆栈
for(int i=head[u];i;i=edg[i].nxt)
{
int v=edg[u].to;
if(v==pre)continue;//不往回遍历
if(vis[v]) low[u]=min(low[u],dfn[v]);
else
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]==dfn[u])
{
int w;
do
{
w=sta[top-1].second;//双连通分量的点
top--;
}while(sta[top-1]!=make_pair(u,v));
w=u;//点u未在循环中遍历到
}
}
}
if(low[u]==dfn[u])top--;//因为u没有第二条路径通向其祖先节点,所以pre-u边出栈
}
一个双连通分量,如果含有一个奇环,则他的每一个点都在一个奇环内。
证明:假设强连通分量G中,a不在任意一个奇环内,而b和c是奇环里的两个点,因为bc是奇环中的两点,所以bc间应该还存在两条路径p和p’,p和p’构成一个奇环。那么p和p’的奇偶性互异。所以如果a经过p构成偶环的话,a经过p’构成奇环。如果a经过p’构成偶环的话,a经过p构成奇环,与a不在任意一个奇环内矛盾。
最终AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int n,m;
#define N 1010
#define M 1000010
int mat[N][N];
struct edg
{
int from,to,nxt;
edg() {}
edg(int f,int t,int nx)
{
from=f;
to=t;
nxt=nx;
}
} edge[M];
int head[N],edgeCnt;
void addedge(int from,int to)
{
edge[edgeCnt]=edg(from,to,head[from]);
head[from]=edgeCnt++;
edge[edgeCnt]=edg(to,from,head[to]);
head[to]=edgeCnt++;
}
int dfn[N],low[N],sta[M],stop,tim,drop[N];
bool vis[N];
int col[N];
bool isCC[N];
int ans;
int que[N*N],qi,qn;
bool judge(int x)
{
for(int i=0; i<n; i++)col[i]=-1;
qn=qi=0;
que[qn++]=x;
col[x]=0;
while(qi<qn)
{
int now=que[qi++];
for(int i=head[now]; i>=0; i=edge[i].nxt)
{
int next=edge[i].to;
if(!isCC[next])continue;
if(col[next]==col[now])
return true;
if(col[next]==-1)
{
que[qn++]=next;
col[next]=col[now]^1;
}
}
}
return false;
}
void tarjan(int now,int pre,int ine)
{
dfn[now]=low[now]=tim++;
vis[now]=1;
sta[stop++]=ine;
for(int i=head[now]; i>=0; i=edge[i].nxt)
{
int next=edge[i].to;
if(next==pre)continue;
if(!vis[next])
{
tarjan(next,now,i);
low[now]=min(low[now],low[next]);
if(dfn[now]==low[next])
{
for(int i=0;i<n;i++)isCC[i]=false;
int cnt=0;
do
{
isCC[edge[sta[stop-1]].to]=true;
stop--;
}
while(sta[stop]!=i);
isCC[now]=true;
if(judge(now))
{
for(int i=0;i<n;i++)
{
if(isCC[i])drop[i]=false;
}
}
}
}
else
{
low[now]=min(low[now],dfn[next]);
}
}
if(low[now]==dfn[now])stop--;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
#endif // ONLINE_JUDGE
while(scanf("%d%d",&n,&m),n||m)
{
int a,b;
for(int i=0; i<n; i++)
{
head[i]=-1;
dfn[i]=low[i]=-1;
col[i]=-2;
drop[i]=true;
vis[i]=0;
for(int j=0; j<n; j++)
mat[i][j]=0;
}
edgeCnt=0;
ans=0;
stop=tim=0;
for(int i=0; i<m; i++)
{
scanf("%d%d",&a,&b);
a--,b--;
mat[a][b]=mat[b][a]=1;
}
for(int i=0; i<n; i++)
{
for(int j=i+1; j<n; j++)
{
if(!mat[i][j])
{
addedge(i,j);
}
}
}
for(int i=0; i<n; i++)
{
if(!vis[i])
{
tarjan(i,-1,-1);
}
}
for(int i=0;i<n;i++)
{
ans+=drop[i];
}
cout<<ans<<endl;
}
return 0;
}

浙公网安备 33010602011771号