浅谈v_DCC缩点
之前有提到过e_DCC缩点,是求出边双连通分量,将每个边双连通分量看作一个点,桥为边,构建新图,v_DCC缩点更复杂一点。
e_DCC中,每个边只会属于一个e_DCC,且桥不会属于任何e_DCC。但是v_DCC就有点不同了,v_DCC中, 割点可能属于多个v_DCC 。OK,那就还是先求割点呗。
求割点,那就tarjan呗,但是有两点要注意
1、如果一个点孤立(没有边相连),它是割点
2、如果tarjan过程中发现当前搜索树的根节点满足判定式 ,它也不一定是割点,它需要有至少两个子节点才行
求点双连通分量就是在求割点基础上加一丢丢操作(有学过有向图强连通分量的tarjan求法的会感觉很相似),多了一个栈,在tarjan求搜索树的过程中,第一次访问到某个点时,将点入栈,当满足割点判定式 ,就将栈中元素依次弹出,知道弹出的元素式的时候,终止弹出即可,然后这些被弹出的点就构成了一个v_DCC。但是,不能像求强连通分量一样,用一个数组记录每个点属于哪个强连通分量,因为割点可能属于多个v_DCC,因此改为,用一个向量(),其中表示第个v_DCC中所有的点,这样就可以将割点放到多个v_DCC中。
如果你还想求每一个点(非割点)属于哪个v_DCC,可以tarjan过程中仿照强连通分量的做法,将每一个被弹出的点直接施加标记,或者在求完所有的v_DCC后,遍历所有的v_DCC,然后施加标记(推荐第二种)
就剩下建图了呗,那就建图呗。e_DCC种建图是以桥为边,连接不同的e_DCC,v_DCC中又与他不同了。在v_DCC中,我们现在求得了所有的割点和v_DCC,假设有个割点,个v_DCC,那么新图中就有这么个点,然后在每个割点与它所在的v_DCC之间连边(可能属于多个v_DCC),就得到了新图。
// 求割点
#include <bits/stdc++.h>
#define mem(a, b) memset(a, b, sizeof a)
using namespace std;
const int N = 310;
int head[N], nex[N], to[N], cnt;
int new_id[N], id;
int head1[N], nex1[N], to1[N], cnt1;
int dfn[N], low[N], num;
bool cut[N];
int root;
int st[N], top;
int v_DCC[N], dcc;
vector<int > vec[N];
int n, m;
void init() {
cnt = 0, num = 0, top = 0, dcc = 0, id = 0;
mem(head, 0), mem(nex, 0), mem(dfn, 0), mem(low, 0), mem(cut, 0), mem(v_DCC, 0);
mem(head1, 0), mem(nex1, 0), mem(new_id, 0), cnt1 = 0, id = 0;
for (int i = 0; i < N; i++)vec[i].clear();
}
void add(int a, int b) {
++cnt;
to[cnt] = b;
nex[cnt] = head[a];
head[a] = cnt;
}
void add1(int a, int b) {
++cnt1;
to1[cnt1] = b;
nex1[cnt1] = head1[a];
head1[a] = cnt1;
}
void tarjan(int x) {
dfn[x] = low[x] = ++num;
int f = 0;
st[++top] = x;
if (head[x] == 0 && x == root) {
cut[x] = 1;
v_DCC[x] = ++dcc;
return;
}
for (int i = head[x]; i; i = nex[i]) {
int y = to[i];
if (!dfn[y]) {
tarjan(y);
low[x] = min(low[x], low[y]);
if (dfn[x] <= low[y]) {
// 有可能是割点
f++;
if (x != root || f > 1) {
// 如果是根节点,需要有至少两个子节点
cut[x] = 1;
}
++dcc;
while (1) {
int t = st[top--];
// v_DCC[t] = dcc;// 不能这样写,因为一个割点可能属于多个点双连通分量
vec[dcc].push_back(t);
if (t == y)break;
}
}
else low[x] = min(low[x], dfn[y]);
}
}
}
/*
v_DCC缩点:
设图中有p个割点,k个点双连通分量,那么新图中应该有p+k割点
将每个割点与它所在的点双连通分量连边,就得到了新图
*/
void rebuild() {
for (int i = 1; i <= n; i++) {
if (cut[i]) {
new_id[i] = ++id;
}
}
for (int i = 1; i <= dcc; i++) {
// 遍历所有的点双连通分量
for (int j : vec[i]) {
if (cut[j]) {
add1(new_id[j] + dcc, i);
add1(i, new_id[j] + dcc);
}
else v_DCC[j] = i;// 非割点的点所属的点双连通分量
}
}
}
int main()
{
init();
cin >> n >> m;
while (m--) {
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) {
root = i;
tarjan(i);
}
}
rebuild();
return 0;
}
作者: correct
出处:https://www.cnblogs.com/correct/p/12861906.html
本站使用「CC BY 4.0」创作共享协议,转载请在文章明显位置注明作者及出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现