20251227 - 点双 割点 割边 总结

比赛链接:https://vjudge.net/contest/777735#problem/A。

割点

介绍

如果从无向图中删去一个点之后,图里的连通分量的数量增加了,那么删去的这个点就被称之为割点。

暴力做法,考虑依次删除每个点,再去数连通分量的个数,判断是否为割点,那么时间复杂度就是 \(O(nm)\) 的,太慢了。一般都是用 Tarjan 做到 \(O(n+m)\) 找割点的。

做法

Tarjan 时,会在 DFS 的过程中维护 \(dfn\) 和 \(low\) 两个信息,其中 \(low_u\) 是指从 \(u\) 出发且最多用反祖边向上走一步的情况下能到达的最小的时间戳。

如果一个点 \(u\),有子节点 \(v\) 不能回到比 \(u\) 更高的节点,那么 \(u\) 就是割点。因为删掉 \(u\) 之后,\(v\) 所在的子树没有反祖边可以连回来,那么图的连通分量数量就会增多,那么 \(u\) 就是割点咯。

我们可以用 \(low\) 和 \(dfn\) 两个数组快速判断是否能连回来,如果 \(low_v \ge dfn_u\) 就代表 \(v\) 这个子节点连不上去了。

这个判断法则只在非根节点时成立。

根节点需要特殊处理。如果根节点在 DFS 树上有两个或更多子节点,根节点是割点,否则如果只有一个子节点就不是割点。证明过于显然所以不写了。

实现

我在代码中用 \(id\) 表示上文中提到的 \(dfn\)。

void DFS(int u,int fa){
    id[u]=(++tot),low[u]=id[u];int cnt=0;
    for(int v:g[u]){
        if(v==fa)continue;
        if(id[v])low[u]=min(low[u],id[v]);
        else{
            DFS(v,u),cnt++;
            low[u]=min(low[u],low[v]);
            if(fa&&low[v]>=id[u])cut[u]=1;
        }
    }if(!fa&&cnt>1)cut[u]=1;return;
}

做法 2

请先把下面的【点双连通分量】部分看完喵。

我们根据割点和点双连通分量的定义,一点也不敏锐地发现一个规律,如果某个点是割点,那么它一定出现在多个点双连通分量中。

那么我们就可以根据这个性质来找割点了。

代码就不放了。其实是因为没写过喵 TAT

割边

介绍

割边也叫桥。

它和割点的定义非常类似:如果从无向图中删去一条边之后,图里的连通分量的数量增加了,那么删去的这个边就被称之为割边。

做法

如果图中没有重边,那么我们只需要把求割点的过程中的 \(low_v \ge dfn_u\) 中的 \(\ge\) 改为 \(>\) 即可,因为割点是以点为单位,割边是以边为单位,但 \(low\) 和 \(dfn\) 记录的都是点的时间戳。

但是,如果图中有重边,那么重边们一定不是割边——显然,你删掉这条后还有另外一条一模一样的,那怎么也不会让图里的连通分量增多。

怎么处理呢?有一种方法,那就是在遍历到从子节点走向父节点的边时,把第一条这样的边忽略,出现第二条时才按照正常的反祖边处理,这样就能在不动原先逻辑的基础上解决重边的问题啦!

实现

与割点的代码特别像。

void DFS(int u,int fa){
    id[u]=(++tot),low[u]=id[u];
    bool flag=0;
    for(auto [v,d]:g[u]){
        if(v==fa){
            if(flag)low[u]=min(low[u],id[v]);
            else flag=1;continue;
        }if(id[v])low[u]=min(low[u],id[v]);
        else{
            DFS(v,u),low[u]=min(low[u],low[v]);
            if(low[v]>id[u])ans.pb(d);
        }
    }return;
}

点双连通分量

介绍

对于某个连通分量,如果任意删去一个点后连通性不变,那么这样的连通分量就被称为点双连通分量。换句话说,如果某个连通分量中不存在任何一个割点,那么该连通分量即为点双连通分量。

点双连通分量也简称点双或 BCC。

做法

有两个重要的性质:

  1. 两个点双连通分量之间最多只有一个公共点,如果有的话,这个点一定是割点。
  2. 对于一个点双连通分量,它在 DFS 树中 \(dfn\) 最小的点一定是割点或者根节点。

根据这两个性质,我们可以用栈记录搜索过的点,搜索到割点之后,在回溯时把点双统计出来即可。

实现

注意一个孤点也可以是一个点双连通分量。

void DFS(int u,int fa){
    id[u]=(++tot),low[u]=id[u];st.push(u);
    for(int v:g[u]){
        if(v==fa)continue;
        if(id[v])low[u]=min(low[u],id[v]);
        else{
            DFS(v,u),low[u]=min(low[u],low[v]);
            if(low[v]>=id[u]){
                vector<int> tmp;tmp.clear();
                tmp.pb(u),cnt[u]++;
                while(tmp.back()!=v)
                    tmp.pb(st.top()),cnt[st.top()]++,st.pop();
                BCC[++len]=tmp;
            }
        }
    }if(!fa&&cnt[u]==0)BCC[++len]={u};return;
}

例题选讲

D - Yet Another Graph Coloration Problem

首先考虑什么时候会无解。显然,如果原图不连通或者图是一棵树那么一定无解。

任选一个根节点跑 DFS,得到一棵 DFS 树,那么原图一定是这棵树加上若干条反祖边。

对于一条反祖边,我们可以考虑把深度较深的那个点的子树部分全部染黑,子树外的染成白色。那么此时黑白两色之间显然有两条不同路径,一条是直接走 DFS 树的,一条是先走 DFS 树到反祖边一端,通过反祖边到另一端接着再走 DFS 树的。

E - BLO-Blockade

首先如果要被禁止进入的城镇 \(u\) 不是割点,那么造成的后果一定只有 \(2(n-1)\)。

而如果是割点,造成的后果就是 \(\sum_{i=1}^{k} sz_i \times (n-sz_i)\)(\(sz_i\) 为 \(u\) 被割掉后剩下的 \(k\) 个连通块的大小情况)。

在求割点时顺便安装上述分类讨论即可。

F - 矿场搭建

先求出所有割点,然后 DFS 搜索所有连通块,统计出每一个连通块中的割点数量。

然后分类讨论:如果该连通块中没有割点,则至少需要建立两个出口,从任意非割点的地方选择两个点建立即可;如果该连通块中有且仅有一个割点,只需要建立一个出口,任意一个非割点的地方都行;如果有两个及以上个割点,则无需建立,可以直接到达其他联通块。

G - 嗅探器

不难发现,如果 \(u\) 是割点且将 \(A,B\) 两点割在两边,那么 \(u\) 就是可行方案之一。

割点是好找的,难点在于如何判断它是否能把 \(A,B\) 两点割在两边。不难发现我们可以根据 DFS 序判定,如果 \(dfn_u > dfn_A\) 且 \(dfn_u < dfn_B\),或者 \(dfn_u < dfn_A\) 且 \(dfn_u > dfn_B\),那么就是可行的。

但是这样会遗漏,如果 \(A,B\) 都在 \(u\) 子树内,其实也可能是可行的。我们想到用 \(u\) 的某个被割掉的儿子节点 \(v\) 来做判断,如果 \(dfn_v > dfn_A\) 且 \(dfn_v < dfn_B\),或者 \(dfn_v < dfn_A\) 且 \(dfn_v > dfn_B\),那么也是可行的。

H - Substring

题目中有一句至关重要的话:

如果该值可以任意大,输出 -1。

为什么可以任意大?这是怎么回事?发生了什么?答案显而易见——有环呗!

我们采用拓扑排序来判环,然后就只剩一个 DAG 了,上 DP。

定义 \(dp_{i,j}\) 表示以 \(i\) 结尾且出现次数最多的字母为 \(j\) 的路径的最大权值。枚举到一条边 \(u \to v\),则有转移 \(dp_{v,i} = \max(dp_{v,i},dp_{u,i}+[s_v=i])\),其中 \([s_v = i]\) 的方括号是艾佛森括号。

最终答案为 \(\max_{u=1}^{n} \max_{j=1}^{26} dp_{i,j}\)。

I - We Need More Bosses

我们想添加的 boss 所处的边一定是一条割边。割边可以提前找出来。

但是你不能把所有割边都放上 boss,只能选取其中的一条路径。为了最大化 boss 数量,考虑给每条边分配边权,割边的边权为 \(1\) 而普通边的边权为 \(0\)。Tarjan 缩点后求树的直径即可,也可以直接在图上求直径。

总结

割点、割边与点双连通分量,是无向图结构分析的核心内容,借助 Tarjan 算法可在 \(O(n+m)\) 的时间复杂度下高效求解。主要用于判定图的连通稳定性,比如定位删去后会割裂图的关键节点(割点)与边(割边),或是划分无割点的稳定连通子结构(点双连通分量)。其应用场景较广,既能用于拓扑优化,也常结合图论其他问题处理连通性相关的逻辑分析,是处理无向图复杂问题的实用工具。

posted @ 2025-12-30 22:17  嘎嘎喵  阅读(64)  评论(1)    收藏  举报