洛谷割点模板题——传送门
割边:在连通图中,删除了连通图的某条边后,图不再连通。这样的边被称为割边,也叫做桥。
割点:在连通图中,删除了连通图的某个点以及与这个点相连的边后,图不再连通。这样的点被称为割点。
DFS搜索树:用DFS对图进行遍历时,按照遍历次序的不同,我们可以得到一棵DFS搜索树。
树边:在搜索树中的蓝色线所示,可理解为在DFS过程中访问未访问节点时所经过的边,也称为父子边
回边:在搜索树中的橙色线所示,可理解为在DFS过程中遇到已访问节点时所经过的边,也称为返祖边、后向边
观察DFS搜索树,我们可以发现有两类节点可以成为割点。对根节点u,若其有两棵或两棵以上的子树,则该根结点u为割点;对非叶子节点u(非根节点),若其中的某棵子树的节点均没有指向u的祖先节点的回边,说明删除u之后,根结点与该棵子树的节点不再连通;则节点u为割点。对于根结点,显然很好处理;但是对于非叶子节点,怎么去判断有没有回边是一个值得深思的问题。我们用dfn[u]记录节点u在DFS过程中被遍历到的次序号,low[u]记录节点u或u的子树通过非父子边追溯到最早的祖先节点(即DFS次序号最小),那么low[u]的计算过程如下。
对于给的例子,其求出的dfn和low数组如下。
id 1 2 3 4 5 6
dfn 1 2 3 4 5 6
low 1 1 1 4 4 4
可以发现,对于情况2,当(u,v)为树边且low[v]≥dfn[u]时,节点u才为割点。而当(u,v)为树边且low[v]>dfn[u]时,表示v节点只能通过该边(u,v)与u连通,那么(u,v)即为割边。tarjan算法的时间复杂度是O(n+m)的,非常快。
——附带码

#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> using namespace std; const int maxn = 100001; int n, m, cnt, rp; int next[2 * maxn], to[2 * maxn], head[maxn], low[maxn], dfn[maxn], father[maxn];//father为父节点 vector <int> cut_point; vector < pair <int, int> > cut_edge; void add(int x, int y) { to[cnt] = y; next[cnt] = head[x]; head[x] = cnt++; } void tarjan(int u) { int i, v, child = 0;//child表示当前节点孩子数量 bool flag = 0; dfn[u] = low[u] = ++rp; for(i = head[u]; i != -1; i = next[i]) { v = to[i]; if(!dfn[v]) { child++; father[v] = u; tarjan(v); if(low[v] >= dfn[u]) flag = 1;//割点 if(low[v] > dfn[u]) cut_edge.push_back(make_pair(min(u, v), max(u, v)));//割边 low[u] = min(low[u], low[v]); } else if(v != father[u]) low[u] = min(low[u], dfn[v]); } //根节点若有两棵或两棵以上的子树则该为割点 //非根节点若所有子树节点均没有指向u的祖先节点的回边则为割点 if((father[u] == 0 && child > 1) || (father[u] && flag)) cut_point.push_back(u); } int main() { int i, j, x, y, s; memset(head, -1, sizeof(head)); scanf("%d %d", &n, &m); for(i = 1; i <= m; i++) { scanf("%d %d", &x, &y); add(x, y); add(y, x); } for(i = 1; i <= n; i++)//图可能不联通(mdzz的洛谷模板题) if(!dfn[i]) tarjan(i); sort(cut_point.begin(), cut_point.end()); s = cut_point.size(); printf("%d ", s); for(i = 0; i < s; i++) printf("%d ", cut_point[i]);//输出割点 s = cut_edge.size(); printf(" %d ", s); for(i = 0; i < s; i++) printf("%d %d ", cut_edge[i].first, cut_edge[i].second);//输出割边 return 0; }
经过培训,发现上面的代码如果有重边就会拉闸。
下面是可以应对重边的代码

1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <string> 5 # include <cmath> 6 # include <vector> 7 # include <map> 8 # include <queue> 9 # include <cstdlib> 10 # define MAXN 2333 11 using namespace std; 12 13 inline void File() { 14 #ifdef DEBUG 15 freopen("in.txt", "r", stdin); 16 #else 17 //freopen(); 18 //freopen(); 19 #endif 20 } 21 22 inline int get_num() { 23 int k = 0, f = 1; 24 char c = getchar(); 25 for(; !isdigit(c); c = getchar()) if(c == '-') f = -1; 26 for(; isdigit(c); c = getchar()) k = k * 10 + c - '0'; 27 return k * f; 28 } 29 30 int n, m, tim, cnt; 31 int dfn[MAXN], low[MAXN], f[MAXN], to[MAXN], next[MAXN], head[MAXN]; 32 vector <int> cut_point; 33 vector < pair <int, int> > cut_edge; 34 35 inline void add(int x, int y) 36 { 37 to[cnt] = y; 38 next[cnt] = head[x]; 39 head[x] = cnt++; 40 } 41 42 inline void dfs(int u, int fa) 43 { 44 int i, v, child = 0; 45 bool flag = 0; 46 dfn[u] = low[u] = ++tim; 47 for(i = head[u]; i != -1; i = next[i]) 48 { 49 if((i ^ 1) == fa) continue; 50 v = to[i]; 51 if(!dfn[v]) 52 { 53 child++; 54 f[v] = u; 55 dfs(v, i); 56 if(low[v] >= dfn[u]) flag = 1; 57 if(low[v] > dfn[u]) cut_edge.push_back(make_pair(min(u, v), max(u, v))); 58 low[u] = min(low[v], low[u]); 59 } 60 else low[u] = min(low[u], dfn[v]); 61 } 62 if((!f[u] && child > 1) || (f[u] && flag)) cut_point.push_back(u); 63 } 64 65 int main() 66 { 67 int i, j, x, y; 68 n = get_num(); 69 m = get_num(); 70 memset(head, -1, sizeof(head)); 71 for(i = 1; i <= m; i++) 72 { 73 x = get_num(); 74 y = get_num(); 75 add(x, y); 76 add(y, x); 77 } 78 for(i = 1; i <= n; i++) 79 if(!dfn[i]) 80 dfs(i, -1); 81 for(i = 0; i < cut_point.size(); i++) printf("%d ", cut_point[i]); 82 puts(""); 83 for(i = 0; i < cut_edge.size(); i++) printf("%d %d ", cut_edge[i].first, cut_edge[i].second); 84 puts(""); 85 return 0; 86 }