割点割边 + 点双边双 学习笔记
割点
定义
在一个无向图(强连通分量仅针对有向图)中,若去掉一个点 u,该图有两点不连通,则称点 u 为割点。
点连通度:该连通(且无向)图的割点数量。
求割点
对于点 u:
-
u 为一个根节点(把图看成树):因为他是根节点,所以它的入度为 0,那么只要它有两个及以上的孩子,那么它就是割点(去掉它了会有两个以上的连通块), c h i l > 1 chil>1 chil>1。
-
u 为子节点:若它的子节点 v 不能够不通过 u 连到 v 的边到 u 的祖先,则只有加上从 u 到 v 的边才能让 v 到 u 的祖先,那么此时 u 就是割点(去掉它之后它的子节点无法和它的祖先相连通), l o w v > = d f n u low_v>=dfn_u lowv>=dfnu。
其他跟求强连通分量的板子相差不大。
void tarjan (int u)
{
dfn[u] = low[u] = ++tmp;
int tot = 0;
for (int i = hd[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (!dfn[v])
{
tot++;
tarjan (v);
low[u] = min (low[u], low[v]);
if ((u == root and tot > 1) or (u != root and dfn[u] <= low[v])) vis[u] = 1;
}
else low[u] = min (low[u], dfn[v]);
}
}
具体根据题目排判断顺序:可以先判 l o w v > = d f n u low_v>=dfn_u lowv>=dfnu,然后处理某些东西,然后再用 u ! = r o o t ∣ ∣ c h i l > 1 u!=root||chil>1 u!=root∣∣chil>1 判断割点。(8.10 比赛的T4)
割边
更割点差不多,就是从点变成了边,定义(例如边联通分量)都相似。
求割边
只要 v 不能不通过 ( u , v ) (u,v) (u,v) 到达 u 或 u 的祖先,那么 ( u , v ) (u,v) (u,v) 就是割边。
话说割点和边真就一个符号的区别。。
void tarjan (int u, int li)
{
dfn[u] = low[u] = ++tmp;
for (int i = hd[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (!dfn[v])
{
tarjan (v, i);
low[u] = min (low[u], low[v]);
if (dfn[u] < low[v]) ans[++cntn] = (abc){min (u, v), max (u, v)};
}
else if (i != find (li)) low[u] = min (low[u], dfn[v]);
}
}
点双
定义
在一无向图中,若该图没有割点,则称它为一点双连通分量。
运用:
-
在一无向图中找出若干点双(8.13 T4 点双);
-
计算还要添加多少节点才能使得原图变成点双(这个不常见吧,
至少到现在也没见着一般边双有这操作)。
求点双
一无向图中,我们是按照割点去分点双的(一割点可能被包含在多个点双中)。
先 Tarjan 求出每一个割点(记得每访问一个节点要压入栈中),
然后每找到一个割点,我们就立马把节点从栈中倒出来,
但是要注意,割点 u 不要倒出来,因为后面它可能还会在另外一个点双中被再次访问到。
void tarjan (int u, int f)
{
dfn[u] = low[u] = ++tmp;
st[++top] = u;
co[u] = 1;
if (u == rt and !hd[u])
{
cout << u << endl;
return;
}
for (int i = hd[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (!dfn[v])
{
tarjan (v, u);
low[u] = min (low[u], low[v]);
if (dfn[u] <= low[v])
{
col++;
while (st[top] != u)
{
cout << st[top] << " ";
co[st[top]] = 0;
if (st[top] == v) {top--;break;}
top--;
}
cout << u << endl;
}
}
else if (v != f) low[u] = min (low[u], dfn[v]);
}
}
边双
这道题可以当作是边双模板吧。。。
定义
嗯,定义和点双类似的。
只要一无向图中没有出现割边,那么它就是边双连通分量。
不同的是,割点可能存在于多个点双中,但割边是不可能存在于任何一个边双中的。(这个很好理解的吧。)
求边双
运用:主要就一个,询问给一无向图添加多少条边才能使得它是边双。
边双是由一条条割边分开来的,我们要判断当前图是否是边双,只需要看它成不成环。
如果能从点 u 找到一条环使得还能从它走回 u,证明点 u 存在于一个边双中(可根据桥的定义理解)。
然后我们进行缩点操作(不过这里的比算法笔记里的简单多了)。
我们只需要在遍历每一条边的时候用割点的思想(是否在同一边双中)判是否需要建这条边,
如果需要,我们只要 d u ( c o ( u i ) ) ← d u ( c o ( u i ) ) + 1 du (co(u_i)) \gets du(co(u_i))+1 du(co(ui))←du(co(ui))+1 以及 d u ( c o ( v i ) ) ← d u ( c o ( v i ) ) + 1 du (co(v_i)) \gets du(co(v_i))+1 du(co(vi))←du(co(vi))+1——两个边双的入、出度加一。
如果不需要直接 continue;
处理下一条边即可。
最后,如果有节点(边双)度数为 1,我们就统计下来——有度数为 1 的点就无法成环。
最后我们让度数为 1 的点两两相连,如果总数为奇数,就给剩下那个点单独一条边就好了—— a n s + 1 > > 1 ans+1>>1 ans+1>>1。
代码
Tarjan (记得双向边建成两条单向边 后面要判断是否已经处理过了。)
inline void tarjan (int u)
{
dfn[u] = low[u] = ++tmp;
st[++top] = u;
for (int i = hd[u]; i; i = e[i].nxt)
if (!vis[i])
{
int v = e[i].to;
vis[i] = vis[i ^ 1] = 1;
if (!dfn[v])
{
tarjan (v);
low[u] = min (low[u], low[v]);
}
else low[u] = min (low[u], dfn[v]);
}
if (dfn[u] == low[u])//是环就直接倒出来
{
co[u] = ++col;
while (st[top] != u)
{
co[st[top]] = col;
top--;
}
top--;
}
}
主函数部分
int main ()
{
memset(hd,0,sizeof(hd));
memset(dfn,0,sizeof(hd));
scanf ("%d %d", &n, &m);
for (int i = 1; i <= m; i++)
{
scanf ("%d %d", &u[i], &v[i]);
add (u[i], v[i]), add (v[i], u[i]);
}
for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan (i);
int ans = 0;
for (int i = 1; i <= m; i++) if (co[u[i]] != co[v[i]]) du[co[u[i]]]++, du[co[v[i]]]++;
for (int i = 1; i <= col; i++) if (du[i] == 1) ans++;
printf ("%d\n", ans + 1 >> 1);
return 0;
}
—— E n d End End——
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现