To be continued…
强连通分量
在有向图G中,如果任意两个不同的顶点相互可达,则称该有向图是强连通的。
Korasaju算法求有向图强连通分量
现在才知道叫这个名字
1.深度优先遍历G,算出每个结点u的结束时间f[u],起
点如何选择无所谓。
2.将G中所有边反向,选择深度优先遍历的起点时,按照结点的结束时间从大到小进行。(用个栈就可以了)。同时进行标记
3. 第2步中产生的标记值相同的结点构成深度优先森林中的一棵树,也即一个强连通分量
bool vis[N];
int scc[N],sccc;
stack<int> S;
void dfs1(int u){
vis[u]=1;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].v;
if(edge[i].ban||vis[v])
continue ;
dfs1(v);
}
S.push(u);
}
void dfs2(int u,int num){
scc[u]=num;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].v;
if(edge[i].ban||scc[v])
continue;
dfs2(v,num);
}
}
void find_scc(){
for(int i=0;i<n;i++){
if(!vis[i])
dfs1(i);
}
for(int i=1;i<=cnt;i+=2){
edge[i].ban=1;
edge[i+1].ban=0;//反向
}
while(!S.empty()){
int x=S.top();
S.pop();
if(scc[x])
continue ;
sccc++;
dfs2(x,sccc);
}
}
Tarjan算法求强连通分量
先来几个定义
dfn为dfs序(即dfs访问顺序)
low[i]表示从i节点出发DFS过程中i下方节点(开始时间大于dfn[i],且由i可达的节点)所能到达的最早的节点的开始时间。
显然,low[i]<=dfn[i]
具体实现感觉很难用文字表述,直接上代码吧
void dfs(int u){
low[u]=dfn[u]=++tot;
instack[u]=1;
S.push(u);
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].v;
if(!dfn[v]){
dfs(v);
low[u]=min(low[u],low[v]);
}
else
if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
scccnt++;
while(1){
int v=S.top();
S.pop();
scc[v]=scccnt;
sz[scccnt]++;
instack[v]=0;
if(u==v)
break;
}
}
}
割边、割点
无向连通图中,如果删除某点后,图变成不连通,则称该点为割点。
无向连通图中,如果删除某边后,图变成不连通,则称该边为割边(又称桥)。
先构建dfs树,dfs树中的父子关系边称为树边
一个顶点u是割点,当且仅当满足(1)或(2)
(1) u为树根,且u有多个直接儿子。
(2) u不为树根,且存在(u,v)为树边,使得
一条边(u,v)是割边,当且仅当(u,v)为树边,且满足
//本算法适用于无重边
//ans1为割点数量,ans2为割边数量
void dfs(int u,int fa){
bool k=1;//避免重复计算割点
low[u]=dfn[u]=++tot;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].v;
if(!dfn[v]){
dfs(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]){
if(dfn[u]==1)
rootsum++;
else if(k){
ans1++;
k=0;
}
if(low[v]>dfn[u])
ans2++;
}
}
else if(v!=fa){
low[u]=min(low[u],dfn[v]);
}
}
if(dfn==1&&rootsum>1)
ans1++;
}
点双连通分量
点双连通分量定义方式1:任两点间有两条点不重复的路径
点双连通分量定义方式2:不含割点的无向连通图
割点可以属于多个点双连通分量,其余点和每条边属于且只属于一个点双连通分量。
其实就是在求割点的时候退栈储存
边双连通分量
边双连通分量定义方式1:任两点间有两条边不重复的路径
边双连通分量定义方式2:不含割边的无向连通图
割边不属于任何一个边双连通分量,其余的边和每个顶点都属于且只属于一个边双连通分量。
其实就是在求割边的时候退栈储存
例题 POJ 3352 Road Construction
给你一个图,要求你加入最少的边,使得最后得到的图为一个边双连通分量
在开始做之前,我们先学习一个性质
一棵有n个叶子结点的树,至少(只需)要添加ceil(n/2)(向上取整)条边,才(就)能转变为一个边双连通分量。
证明
首先,显然,一条边最多只能连接两个叶子结点,因而至少是ceil(n/2)条边
下证ceil(n/2)条边一定能保证成为一个边双连通分量
首先证明n为偶数时其成立
设两个叶子节点a,b
现在我们想找到离a、b最近的a’、b’是度数>=3的节点
这样的节点不存在当且仅当整个树是一条链(或者可以拉成一条链)且a、b是链的两端,那么我们连接a、b即可
当a’≠b’时
我们连接a、b,再缩点
则减少了两个叶子结点,由于a’、b’度数>=3,所以没有新增叶子节点
当a’=b’时
由于a’度数>=3,则必存在c为叶子结点且a≠c,b≠c
连接a、c则减少两个叶子结点,没有新增叶子结点
综上,n为偶数时,n/2条边可以将其缩为一个点(即成为边双联通分量)
当n为奇数时,我们可以先按上面方法连接n-1个叶子结点,共用了floor(n/2)(向下取整)条边,最后再使那个节点与前面缩成的节点相连即可
综上,证毕
所以上面那道题,首先跑tarjan,缩点重构图,然后统计叶子结点数目再套结论即可