双连通分量 总结及例题

点双连通和边双连通

  • 连通的概念:在无向图中,所有点能互相到达
  • 连通分量:互相联通的子图
  • 点双连通:删掉一个点之后,图仍联通
  • 边双连通:删掉一条边之后,图仍联通

tarjan 算法:

该算法是R.Tarjan发明的。对图深度优先搜索, dfn[i]为第i个结点在搜索树中的深度,low[i]为第i个结点的子树的所有儿子连接到的最上面的结点层数。根据定义,则有:

Low(u)=Min
{
    dfn(u),
    dfn(v) ,(u,v)为后向边(返祖边) 等价于 DFS(v)<DFS(u)且v不为u的父亲节点
    Low(v) ,(u,v)为树枝边(父子边)
}

一个顶点u是割点,当且仅当满足(1)或(2)
(1) u为树根,且u有多于一个子树。
(2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得DFS(u)<=Low(v)。
一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)

求双连通分量

  1. 对于点双连通分量,实际上在求割点的过程中就能顺便把每个点双连通分量求出。建立一个栈,存储当前双连通分量,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满足DFS(u)<=Low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分量,其余点和每条边只属于且属于一个点双连通分量支。
    (这种还没有实现过,不过我认为显然如果把割点标记出来,跑dfs也能求出点双连通分支,虽然代码量会上升,不过还挺好打的,下面例题二类似)
  2. 对于边双连通分量,求法更为简单。只需在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分量。桥不属于任何一个边双连通分量,其余的边和每个顶点都属于且只属于一个边双连通分量。

构造双连通图

  1. 一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1
  2. 统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。

模板

int root, cnt;
int vis[maxn], dfn[maxn], low[maxn];
bool cut[maxn];
//vector<pair<int, int>>bridge;

void dfs(int u, int fa)
{
    int son=0;
    vis[u]=1;
    dfn[u]=low[u]=++cnt;
    for (int i=0; i<G[u].size(); i++)
    {
        int v=G[u][i];
        if (v==fa) continue;
        if (vis[v]==1) low[u]=min(low[u], dfn[v]); //返祖边
        if (vis[v]==0)
        {
            dfs(v, u);
            son++;
            low[u]=min(low[u], low[v]);
            if ( (u==root && son>1) || (u!=root && low[v]>=dfn[u]))
            {
                cut[u]=true;
                //if(low[v] > dfn[u]) bridge.push_back({u, v}); //(u, v) 是桥
            }
        }
    }
    vis[u]=2;
}

void tarjan_init()
{
    memset(vis, 0, sizeof(vis));
    memset(cut, 0, sizeof(cut));
    cnt=0; root=1;
    //bridge.clear();
}

例题

POJ1144【基础】
题目大意:
给出一个无向图,求出有多少个割点。
输入:
有若干组测试数据。每一组测试数据的第一行有一个整数 n,表示有 n
(1<=n<100)个点,n=0 时测试数据结束。接下来有若干行,每一行第一个整
数 u 表示这一行描述的是以 u 为起点的边,接下来有若干个整数 vi 表示有一条
边 u-vi,u=0 时表示这一组测试数据结束。
输出:
对于每一组测试数据,输出一个整数,即有多少个割点。

模板题,没太多要说的

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstdlib>
#include<cstring>
using
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值