双联通分量---点双联通,边双联通 (模板)

转载自@vufw_795

定义:

对于一个连通图,如果任意两点至少存在两条点不重复路径,则称这个图为点双连通的(简称双连通);如果任意两点至少存在两条边不重复路径,则称该图为边双连通的。点双连通图的定义等价于任意两条边都同在一个简单环中,而边双连通图的定义等价于任意一条边至少在一个简单环中。对一个无向图,点双连通的极大子图称为点双连通分量(简称双连通分量),边双连通的极大子图称为边双连通分量。这篇博客就是总结一下求解无向图点双连通分量与边双连通分量的方法。

算法:

求解点双连通分量与边双连通分量其实和求解割点与桥密切相关。不同双连通分量最多只有一个公共点,即某一个割顶,任意一个割顶都是至少两个点双连通的公共点。不同边双连通分量没有公共点,而桥不在任何一个边双连通分量中,点双连通分量一定是一个边双连通分量。

下面首先介绍点双连通分量的Tarjan算法:

当我们找到割顶的时候,就已经完成了一次对某个极大点双连通子图的访问,那么我们如果在进行DFS的过程中将遍历过的点保存起来,是不是就可以得到点双连通分量了?为了实现算法,我们可以在求解割顶的过程中用一个栈保存遍历过的边(注意不是点!因为不同的双连通分量存在公共点即割顶),之后每当找到一个点双连通分量,即子结点v与父节点u满足关系low[v]>=dfn[u],我们就将栈里的东西拿出来直到遇到当前边。

这里注意放入栈中的不是点,而是边,这是因为点双连通分量是存在重复点的,如果我们放入栈中的是点,那么对于某些点双连通分量,就会少掉一些点(这些点都是割顶)。

#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
using namespace std;
#define clr(a,b) memset(a,b,sizeof(a))
#define pb(a) push_back(a)
#define fir first
#define se second
#define LL unsigned long long
typedef pair<int,int> pii;
typedef pairint> pli;
typedef pair pll;
const int maxn = 1e5+5;
const int inf = 0x3f3f3f3f;
LL mod = 1e9+7;

struct node  
{  
    int x,y;  
    node(int a=0, int b=0)  
    {  
        x=a;y=b;  
    }  
}tn;  
//blocks[]存每个块包含的点,bridge存是桥的边  
vector<int>g[maxn],blocks[maxn];  
vectorbridge;  
stackst;  
// 深度优先搜索访问次序, 能追溯到的最早的次序  
int dfn[maxn],low[maxn];  
bool vis[maxn];  
// 索引号,块的个数  
int index,cnt,ans1,ans2;  
int n,m;  

void init()  
{  
    for(int i=0;iwhile(!st.empty())st.pop();  
    memset(dfn, 0, sizeof(dfn));  
    memset(low, 0, sizeof(low));  
    index = cnt = 1; 
    ans1 = ans2 = 0;
}  

void judge(int u,int v)  
{  
    int x,y,c1 = 0,c2 = 0;  
    node temp;  
    memset(vis,false,sizeof(vis));  
    while(!st.empty())  
    {  
        c1++;
        temp = st.top();st.pop();  
        x=temp.x;y=temp.y;  
        if(!vis[y]) c2++,blocks[cnt].push_back(y),vis[y]=true;  
        //一直到最后一条树枝边为止,起点并不属于这个双连通分量,如果属于,已在后向边中加入。  
        if(x==u) break;  
        if(!vis[x]) c2++,blocks[cnt].push_back(x),vis[x]=true;  
    }  
    if(c1 > c2) ans2 += c1;
    cnt++;  
}  

void tarjan(int x,int fa)  
{  
    low[x] = dfn[x] = index++;  
    int len = g[x].size();  
    for(int i=0;iint t=g[x][i];  
        if(t==fa)  
            continue;  
        if(!dfn[t] && dfn[t]//加入树枝边  
            st.push(node(x,t));  
            tarjan(t,x);  
            low[x] = min(low[x], low[t]);  
            if(dfn[x]<=low[t]) {
                if(dfn[x] < low[t]) {
                    ans1++;
                }
                judge(x,t);  
            }
            if(dfn[x]else if(dfn[t] < dfn[x])  
        {  
            //加入后向边  
            st.push(node(x,t));  
            low[x] = min(low[x], dfn[t]);  
        }  
    }  
}  

int solve()  
{  
    for(int i = 1; i <= n; i++)  
        if(!dfn[i]) {
            tarjan(i,i);  
        }
    return cnt;  
}  

int main() {
    while(cin>>n>>m && (n || m)) {
        init();
        for(int i = 1;i <= m;i++) {
            int u,v;
            scanf("%d%d",&u,&v);
            g[u+1].pb(v+1);
            g[v+1].pb(u+1);
        }
        solve();
        printf("%d %d\n",ans1,ans2);
    }
}

这里需要十分注意的是,算法结束之后,每个结点会有一个编号,代表它属于哪一个点双连通分量,但是,割顶的编号是完全没有意义的!这个算法灵活使用了两个时间戳和栈,完成了点双连通分量的发现。

例题:UVALIVE 5135

之后介绍边双连通分量的求解算法:

边双连通分量的求解非常简单,因为边双连通分量之间没有公共边,而且桥不在任意一个边双连通分量中,所以算法十分简单,即先一次DFS找到所有桥,再一次DFS(排除了桥)找到边双连通分量。

PS:当然可以用一次DFS实现。

struct Edge{
    int u,v;
    Edge(int u=0,int v=0):u(u),v(v){}
}e[maxm];
int n,m,stamp,dfn[maxn],low[maxn],bccno[maxn],bcc_cnt;
vector<int> vec[maxn],bcc[maxn];
bool g[maxn][maxn],isbridge[maxm];

void tarjan(int index,int fa)
{
    int tmp;
    dfn[index]=low[index]=++stamp;
    for(int i=0;iif(!dfn[tmp])
        {
            tarjan(tmp,index);
            low[index]=min(low[index],low[tmp]);
            if(low[tmp]>dfn[index])
                isbridge[vec[index][i]]=isbridge[vec[index][i]^1]=1;
        }
        else if(dfn[tmp]void dfs(int index)
{
    dfn[index]=1;
    bccno[index]=bcc_cnt;
    for(int i=0;iint tmp=vec[index][i];
        if(isbridge[tmp])
            continue;
        if(!dfn[e[tmp].v])
        {
            dfs(e[tmp].v);
        }
    }
}

void find_ebcc(){
    bcc_cnt=stamp=0;
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(isbridge,0,sizeof(isbridge));
    memset(bccno,0,sizeof(bccno));
    memset(bcc,0,sizeof(bcc));
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            tarjan(i, -1);
    memset(dfn,0,sizeof(dfn));
    for(int i=1;i<=n;i++)
    {
        if(!dfn[i])
        {
            bcc_cnt++;
            dfs(i);
        }
    }               
}

你可能感兴趣的:(连通图,模板)