『Tarjan算法 无向图的割点与割边』

Posted parsnip

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了『Tarjan算法 无向图的割点与割边』相关的知识,希望对你有一定的参考价值。

<更新提示>

<第一次更新>


<正文>

无向图的割点与割边

定义:给定无相连通图(G=(V,E))
若对于(x in V),从图中删去节点(x)以及所有与(x)关联的边后,(G)分裂为两个或以上不连通的子图,则称(x)(G)的割点。
若对于(e in E),从图中删去边(e)之后,(G)分裂为两个不连通的子图,则称(e)(G)的割边。

对于很多图上问题来说,这两个概念是很重要的。我们将探究如何求解无向图的割点与割边。

预备知识

时间戳

图在深度优先遍历的过程中,按照每一个节点第一次被访问到的顺序给(N)个节点(1-N)的标记,称为时间戳,记为(dfn_x)

追溯值

设节点(x)可以通过搜索树以外的边回到祖先,那么它能回到祖先的最小时间戳称为节点(x)的追溯值,记为(low_x)。当(x)没有除搜索树以外的边时,(low_x=x)

Tarjan 算法

著名的(Tarjan)算法可以在线性时间内求解无向图的割点与割边。

我们来了解一下(Tarjan)算法。

割点判定法则

存在(v)满足(dfn_uleq low_v(v in Son(u)))时,(u)为图的一个割点
特别地,(u)为根节点时,需要有两个子节点(y_1,y_2)满足要求。

证明:
(S)是从根(r)(u)的轨上含(r)不含(u)的一切点组成的集合,(T)是以(v)为根的子树上的点集。易知不存在连接(T)(V-(S∪{u}∪T))的边。若存在连接(t∈T)(s∈S)的边(ts),则它是返祖边,且(dfn_s<dfn_u)。这时(low_v ≤ dfn_s ≤ dfn_u),与已知(low_v ≥ dfn_u)矛盾,故(ts)这种边不存在,故(u)是割顶,证毕.

我的理解:
对于任意的点(u),存在(v)满足(dfn_uleq low_v(v in Son(u)))时,就说明(u)的子节点无论怎样都无法通过其他边回到(u)的父节点,也就是说,(u)以后的部分就相当于"孤立"了,删去(u),图的联通分量必然增加。

依此,我们可以用递归的形式实现求解(dfn)以及(low)数组,并利用判定法则找到割点。

(Code:)

inline void Tarjan(int x,int root)
{
    dfn[x]=low[x]=++cnt;
    int flag=0;
    for(int i=Last[x];i;i=e[i].next)
    {
        int y=e[i].ver;
        if(!dfn[y])
        {
            Tarjan(y,root);
            low[x]=min(low[x],low[y]);
            if(low[y]>=dfn[x])
            {
                flag++;
                if(x!=root||flag>1)cutvertex[x]=true;
            }
        }
        else low[x]=min(low[x],dfn[y]);
    }
}

割边判定法则

存在(v)满足(dfn_u < low_v(v in Son(u)))时,((u,v))为图的一条割边

我们可以用类似于割点判定法则的方法证明割边判断法则,这里不再赘述。

值得我们注意的是,在求解割点时,由于判定法则是:

存在(v)满足(dfn_uleq low_v(v in Son(u)))时,(u)为图的一个割点

所以我们不在乎程序实现时(v)是否会枚举到(u)的父亲节点(不会对答案造成影响),但是,我们在求割边时,v枚举到u的父亲会带来错误,所以我们利用异或运算和"成对变换"的技巧避免通过无向边回到父亲节点(也称位运算卡掉二元环)。

具体地,如(2 xor 1=3,3 xor 1=2),我们将两条有向边组成的无向边存在(2)(3)两个下标中,得以互相转换。

(Code:)

inline void Tarjan(int x,int inedge)
{
    dfn[x]=low[x]=++cnt;
    for(int i=Last[x];i;i=e[i].next)
    {
        int y=e[i].ver;
        if(!dfn[y])
        {
            Tarjan(y,i);
            low[x]=min(low[x],low[y]);
            if(low[y]>dfn[x])
                bridge[i]=bridge[i^1]=true;
        }
        else if(i!=(inedge^1))
            low[x]=min(low[x],dfn[y]);
    }
}

交换机

Description

n个城市之间有通讯网络,每个城市都有通讯交换机,直接或间接与其它城市连接。因电子设备容易损坏,需给通讯点配备备用交换机。

但备用 交换机数量有限,不能全部配备,只能给部分重要城市配置。

于是规定:如果某个城市由于交换机损坏,不仅本城市通讯中断,还造成其它城市通讯中断,则配备备 用交换机。

请你根据城市线路情况,计算需配备备用交换机的城市个数,及需配备备用交换机城市的编号。

友情提示:图论常见的坑点,重边,自环,还有对本题来说的不连通

Input Format

第一行,一个整数n,表示共有n个城市(2<=n<=20000)

下面有若干行(<=60000):每行2个数a、b,a、b是城市编号,表示a与b之间有直接通讯线路。

Output Format

第一行,1个整数m,表示需m个备用交换机。

下面有m行,每行有一个整数,表示需配备交换机的城市编号。

输出顺序按编号由小到大。如果没有城市需配备备用交换机则输出0。

Sample Input

7
1 2
2 3
2 4
3 4
4 5
4 6
4 7
5 6
6 7

Sample Output

2
2
4

解析

这是一道Tarjan求割点模板题,借此给出Tarjan求割点的完整代码。

(Code:)

#include<bits/stdc++.h>
using namespace std;
const int N=90000,M=150000;
int n,Last[M],t,cutvertex[N],dfn[N],low[N],cnt,m,ans[N];
struct edge{int ver,next;}e[M];
inline void insert(int x,int y)
{
    e[++t].ver=y;e[t].next=Last[x];Last[x]=t;
}
inline void input(void)
{
    scanf("%d",&n);
    int x,y;
    while(~scanf("%d%d",&x,&y))
        if(x^y)insert(x,y),insert(y,x);
}
inline void Tarjan(int x,int root)
{
    dfn[x]=low[x]=++cnt;
    int flag=0;
    for(int i=Last[x];i;i=e[i].next)
    {
        int y=e[i].ver;
        if(!dfn[y])
        {
            Tarjan(y,root);
            low[x]=min(low[x],low[y]);
            if(low[y]>=dfn[x])
            {
                flag++;
                if(x!=root||flag>1)cutvertex[x]=true;
            }
        }
        else low[x]=min(low[x],dfn[y]);
    }
}
int main(void)
{
    input();
    for(int i=1;i<=n;i++)
        if(!dfn[i])Tarjan(i,i);
    for(int i=1;i<=n;i++)
    {
        if(cutvertex[i])
            ans[++m]=i;
    }
    if(m)printf("%d
",m);
    else printf("0
");
    for(int i=1;i<=m;i++)
        printf("%d
",ans[i]);      
}

交换机

Description

天凯是苏联的总书记。苏联有n个城市,某些城市之间修筑了公路。任意两个城市都可以通过公路直接或者间接到达。 天凯发现有些公路被毁坏之后会造成某两个城市之间无法互相通过公路到达。这样的公路就被称为dangerous pavement。 为了防止美帝国对dangerous pavement进行轰炸,造成某些城市的地面运输中断,天凯决定在所有的dangerous pavement驻扎重兵。可是到底哪些是dangerous pavement呢?你的任务就是找出所有这样的公路。

Input Format

第一行n,m(1<=n<=100000, 1<=m<=300000),分别表示有n个城市,总共m条公路。

以下m行每行两个整数a, b,表示城市a和城市b之间修筑了直接的公路。

Output Format

输出有若干行。每行包含两个数字a,b(a < b),表示 < a,b >是dangerous pavement。请注意:输出时,所有的数对< a,b>必须按照a从小到大排序输出;如果a相同,则根据b从小到大排序。

Sample Input

6 6
1 2
2 3
2 4
3 5
4 5
5 6

Sample Output

1 2
5 6

解析

这是一道Tarjan求割边模板题,借此给出Tarjan求割边的完整代码。

(Code:)

#include<bits/stdc++.h>
using namespace std;
const int N=100000+200,M=300000+200;
int n,m,t=1,Last[M*2],dfn[N],low[N],bridge[N],cnt,tot;
struct edge{int ver,next;}e[M*2];
pair < int,int > ans[N];
inline void insert(int x,int y)
{
    e[++t].ver=y;e[t].next=Last[x];Last[x]=t;
}
inline void input(void)
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        insert(x,y);
        insert(y,x);
    }
}
inline void Tarjan(int x,int inedge)
{
    dfn[x]=low[x]=++cnt;
    for(int i=Last[x];i;i=e[i].next)
    {
        int y=e[i].ver;
        if(!dfn[y])
        {
            Tarjan(y,i);
            low[x]=min(low[x],low[y]);
            if(low[y]>dfn[x])
                bridge[i]=bridge[i^1]=true;
        }
        else if(i!=(inedge^1))
            low[x]=min(low[x],dfn[y]);
    }
}
int main(void)
{
    input();
    for(int i=1;i<=n;i++)
        if(!dfn[i])Tarjan(i,0);
    for(int i=2;i<t;i+=2)
        if(bridge[i])
        {
            if(e[i].ver>e[i^1].ver)swap(e[i].ver,e[i^1].ver);
            ans[++tot]=make_pair(e[i].ver,e[i^1].ver);
        }
    sort(ans+1,ans+tot+1);
    for(int i=1;i<=tot;i++)
        printf("%d %d
",ans[i].first,ans[i].second);
    return 0;
}


<后记>

以上是关于『Tarjan算法 无向图的割点与割边』的主要内容,如果未能解决你的问题,请参考以下文章

Tarjan算法:求解图的割点与桥(割边)

图的割点与割边(超详细!!!)

Tarjan算法与割点割边

hihoCoder 1183 连通性一·割边与割点(Tarjan求割点与割边)

图的割边--仅仅与割点有一个等号的差别

ZOJ Problem - 2588 Burning Bridges tarjan算法求割边