割点和割边

定义

割点的定义(均在无向图中):在一个连通图中,如果有一个顶点集合,删除这个集合中的点以及相关的边之后,连通块的数量会增多.我们就称这个顶点集合为割点集合.如果这个点集合中只有一个点,那么这个点就叫做割点.
割边的定义(均在无向图中):在一个连通图中,如果删去其中一条边后,连通块的数量会增多,那么我们称这条边为桥或者是割边.

例如这个图:A和B都是割点,其中A-B为桥或割边
这里写图片描述

性质

1).若割点将一个图划分为两个部分U与W,那么所有的u∈U,v∈W,v 处于u到v的每一条路上.
2).每个非平凡的连通图中至少有两个点不是割点.
证明:每个非平凡的连通图中均存在一棵生成树,而非平凡的树上至少有两个度为1的顶点,而这两个顶点正好就是不是割点的两个点

DFS搜索树

这里写图片描述
1->2->3->5->4,然后我们可以根据这个序列画出一棵树.

我们把绿色的边称作树边(tree edge),黑色的边称作回边(back edge),通过回边可以访问到之前已经访问过的点.注意,我们常常在遇到一个点去访问它的父节点时,不会将回边表示出来.(如2与3)
这样,我们就得到了一棵DFS搜索树
这里写图片描述

Tarjan算法

首先选择一个根节点,从它开始进行DFS遍历.
对于根节点来说,判断它是否为割点是十分容易的.我们只需要看它的字树的数量即可.若它的子树的数量>=2,那么它必定为一个割点.(使得子树与子树之间失去了联系)
那么我们只需要求非根节点是否为割点即可.

首先,我们需要定义两个数组:low[]和dfn[].
dfn[u]为u点在上述dfs中是被访问到的第几个点。
low[u]为从u点及u点的子孙出发仅经过一次回边能到达的最小dfn。(如上图low[2]=1 2>3>1)

割点

如果一条边满足low[v]>=dfn[u],那么u点即为一个割点.
证明:如果存在这样一条边满足这样的性质,那么u的儿子v就永远不会访问到早于u的点,那么也就是说,从v出发形成的环中不会包括u,那么从u断开的话,就会形成两个或多个连通块,满足了割点的需求.

首先,low[u]可以先初始化为dfn[u],我们认为u至少能通过回边访问到自己(其实也就是没有回边).然后我们在遍历的过程中,考虑两种情况:一种是v点还没有被访问过,那么它的low[u]=min(low[u],low[v])(此时low[v]已经在dfs的过程中求出来了(在回溯的过程中),v能访问到的最早的点u同样也能访问到).
在过程中如果访问到一个已经访问过的点,那么有low[u]=min(low[u],dfn[v]).然后返回.

割边

割边的求法类似于割点.之前我们说到,在判断非根节点是否为割点时,我们采用了看low[v]>=dfn[u]的做法,在这里我们的做法更加简单.
不需要考虑根节点的问题,只需要判定low[v]>dfn[u]即可.(如之前给出来的图)

例题

题目描述

给出一个无向连通图, 求出所有割点与割边的数量。

输入

第1行: 2个整数N,M (1 <= N <= 5,000,N-1 <= M <= 10,000),分别表示顶点数和边数
接下来M行,每行2个整数,表示图中的一条边。

输出

第1行:1个整数,表示割点数
第2行:1个整数,表示割边数

样例输入

11 13
1 2
1 4
1 5
1 6
2 11
2 3
4 3
4 9
5 8
5 7
6 7
7 10
11 3

样例输出

4
3

代码

#include<cstdio>
#define MAXN 5000
#define MAXM 10000
struct node {
    int v;
    node *next;
}edge[MAXM * 2 + 5];
node *adj[MAXN+5];
node *ecnt = &edge[0];
int dfn[MAXN+5], low[MAXN+5];
bool vis[MAXN+5];
int n, m;
int cp = 0, ce = 0; 
int dcnt = 0;
int rtson = 0;
void AddEdge(int u, int v) {
    node *p = ecnt++;
    p->v = v, p->next = adj[u], adj[u] = p;

    p = ecnt++;
    p->v = u, p->next = adj[v], adj[v] = p;
}
void Init() {
    scanf("%d%d", &n, &m);
    for(int i = 0; i < m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        AddEdge(u, v);
    }
}

inline int min(int a, int b) {
    return a < b ? a : b;
}

void dfs(int u, int fa) {
    low[u] = dfn[u] = ++dcnt;
    for(node * p = adj[u]; p != NULL; p = p->next) {
        int v = p->v;
        if(dfn[v] == 0) {
            dfs(v, u);
            low[u] = min(low[u], low[v]);
            if(low[v] >= dfn[u]) {
                if(fa != -1) {
                    if(!vis[u]) {
                        cp++;
                        vis[u] = true;
                    }
                }
                else
                    rtson++;
            }
            if(low[v] > dfn[u])
                ce++;
        }
        else
            if(v != fa)
                low[u] = min(low[u], dfn[v]);
    }
}
int main() {
    Init();
    dfs(1, -1);
    if(rtson > 1) cp++;
    printf("%d\n%d\n", cp, ce);
}
  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值