图的连通性(25.10.13)
图的连通性
概述
首先有几个大的概念:
极大:扩大到不能再扩大了,包含所有符合条件的内容强连通分量:指在某个子图内,任意两个点之间都有路径可以到达,这个子图在极大的时候,称为强连通分量
双连通:指在两点之间,若选择任意一条边删去,但两点仍连通,称两点双连通
边/点双连通分量:指在某个无向子图内,极大的边/点双连通子图
传递性:边双连通可传递,点双连通不可传递
a——b,b——c为边双连通,则a——c为边双连通
a——b,b——c为点双连通,则a——c不一定为点双连通割点:指删掉某个点可以使极大连通分量数量增加
割边(桥):指删掉某个边可以使极大连通分量数量增加针对这些需要一个算法:tarjan
用dfn记录dfs序(生成树)下此点的顺序
用low记录此点可以从非树边回到的最早dfn序节点
双连通分量
点双连通分量
本质是判断所有割点,之后按连通性做判断,使用栈进行操作,可以有效的去防止割点的延伸,把所有连通子图输出
#include<bits/stdc++.h> using namespace std; const int N = 5*1e5+10; const int M = 5*1e6+1000; int n,m; int cnt=1, h[N]; struct node { int nex, to; }e[M*2]; void add(int u, int v) { e[++cnt].nex = h[u]; e[cnt].to = v; h[u] = cnt++; } int dfn[N], low[N], cnt_dfn=0, root; int vis[N]; // int c[N], num_c=0; int stack_c[N], top=0, cnt_c=0;//多了一个栈的处理 vector<int> ans[M]; void tarjan(int u, int last) {//tarjan去求出dfn和low dfn[u] = low[u] = ++cnt_dfn; stack_c[++top] = u;//放点入栈 if (u==root&&h[u]==0) { ans[++cnt_c].push_back(u);//如果是根节点并且无子边,说明是单点 return; } int count = 0; for (int i=h[u]; i; i = e[i].nex) { int v = e[i].to; if (!dfn[v]) { tarjan(v,i); low[u] = min(low[u],low[v]); // 此时u是割点,v所在的子树与u构成一个新的V-DCC // 想法是u可能会多次应用,发现了一个割点就将其与v所在子树的所有存进去,之后断掉子树找新的 // 因为是递归,所有的v的子树早就处理好了,而且是先把v下头的所有DCC处理了递归上来的 if (low[v]>=dfn[u]) {//发现了新的断点,下头点没法回上头分量了 count++; if (count>1||root!=u) {//对于根节点的操作 vis[u] = 1; } cnt_c++; int z; do { z=stack_c[top--];//把栈挨着弹出 ans[cnt_c].push_back(z); }while (z!=v);// 循环直到弹出v(子树的起点) //割点加入V-DCC ans[cnt_c].push_back(u); } }else if (i!=(last^1)) { low[u] = min(low[u],dfn[v]); } } } int main() { cin>>n>>m; for (int i=1; i<=m; i++) { int u, v; cin>>u>>v; if (u==v) continue; add(u,v); add(v,u); } for (int i=1; i<=n; i++) { if (!dfn[i]) { root = i; tarjan(i,0); } } cout<<cnt_c<<"\n"; for (int i=1; i<=cnt_c; i++) { cout<<ans[i].size(); for (int j=0; j<ans[i].size(); j++) { cout<<" "<<ans[i][j]; } cout<<"\n"; } return 0; }
边双连通分量
本质是判断所有割边,然后除了割边的所有连通分量统计记录
#include <bits/stdc++.h> using namespace std; const int N = 5*1e5+100; const int M = 2*1e6+100; int n,m; vector<int> ed[N*2]; //链式前向星 int cnt=1, h[N]; struct node { int nex, to; }e[M*2]; void add(int u, int v) { e[++cnt].to = v; e[cnt].nex = h[u]; h[u] = cnt; } bool vis[M*2]; //tarjan int dfn[N], low[N], cnt_dfn=0;//时间戳 // int d[N], dc, in_stack[N], dcc[N];//栈,指针,是否在栈,每个点所在的dcc的编号 void tarjan(int u, int fa) {//求割边,但是可以直接在这里处理,用个栈 dfn[u] = low[u] = ++cnt_dfn; for (int i=h[u]; i; i=e[i].nex) { int v=e[i].to; if (!dfn[v]) { tarjan(v,i); low[u] = min(low[u],low[v]); if (dfn[u]<low[v]) { vis[i] = vis[(i^1)] = true; } } else if (i!=(fa^1)) low[u] = min(low[u],dfn[v]); } } //dfs序 int c[N], dcc=0; void dfs(int u) { c[u] = dcc; if (u) ed[dcc].push_back(u); for (int i=h[u]; i; i=e[i].nex) { int v=e[i].to; if (c[v]||vis[i]) continue; dfs(v); } } int main() { memset(low,0x3f, sizeof(low)); cin>>n>>m; for (int i=1; i<=m; i++) { int u, v; cin>>u>>v; add(u, v); add(v, u); } for (int i=1; i<=n; i++) if (!dfn[i]) tarjan(i,0); for (int i=1; i<=n; i++) if (!c[i]) { dcc++; dfs(i); } cout<<dcc<<endl; for (int i=1; i<=dcc; i++) { cout<<ed[i].size()<<" "; for (int j=0; j<ed[i].size(); j++) cout<<ed[i][j]<<" "; cout<<endl; } return 0; }
思考方向
在看到一个图之后,