如果不知道什么双连通: 双连通分量
这题和以前的双连通有点不太一样,以前求得时候都是整个图的割边,这里求得是两点间的割边,结题报告上说了一种暴力的方法,首先求出所有割边,然后求一次最短路,则割边和最短路径求一次交集,可以得到S, T间的桥,很不错的思路,如果没想法了,确实可以暴力一下,这题还可以用dfs + 记录的方法,直接求得S, T间的桥,dfs的时候,在每个桥边(u, v)都需要回溯,判断一下,如果点v能到达了T,则此边必是必经之路,因为dfs是从源点S开始搜的,证明S能到v,而(u, v)是桥,v又能到达T,则可以断定边(u, v)即为所求,具体实现见代码。
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1
/*
求两点间的割边,按升序输出,或叫必经之路上的边
*/
2 #include < stdio.h >
3 #include < stdlib.h >
4 #include < string .h >
5 #define NN 10004
6 #define MM 200010
7 int idx; // 边数
8 int bCnt; // 连通分支个个数 branch cnt
9 int time; // 记录时间戳
10 int low[NN]; // Low(u)为u或u的子树中能通过非父子边追溯到的最早的节点,即DFS序号最小的节点
11 int dfn[NN]; // 深度优先标号
12 int bel[NN]; // bel[u] = k,节点u属于分支k
13 int mark[NN];
14 int stk[MM];
15 int N, M, top;
16 typedef struct node{
17 int v;
18 int vis;
19 int id;
20 struct node * nxt, * op;
21 }NODE;
22
23 NODE edg[MM];
24 NODE * link[NN];
25 NODE * par[NN]; // 记录到达顶点v的树边
26
27 int Min( int a, int b){
28 return a < b ? a : b;
29 }
30
31 int cmp( const void * a, const void * b){
32 return * ( int * )a - * ( int * )b;
33 }
34 void Add( int u, int v, int id){ // 加边
35 idx ++ ;
36 edg[idx].v = v;
37 edg[idx].vis = 0 ;
38 edg[idx].nxt = link[u];
39 link[u] = edg + idx;
40 edg[idx].op = edg + idx + 1 ; // 表示反向边
41 edg[idx].id = id;
42 idx ++ ;
43 edg[idx].v = u;
44 edg[idx].vis = 0 ;
45 edg[idx].nxt = link[v];
46 link[v] = edg + idx;
47 edg[idx].op = edg + idx - 1 ;
48 edg[idx].id = id;
49 }
50
51 int dfs( int u){ // 1表示可以到达点N - 1,0表示不可到达
52 int v, tmp;
53 dfn[u] = low[u] = ++ time;
54 int ok = 0 ;
55 if (u == N - 1 ){
56 ok = 1 ;
57 }
58 for (NODE * p = link[u]; p; p = p -> nxt){
59 if (p -> vis) continue ;
60 p -> vis = p -> op -> vis = 1 ; // 由于加的是双向边,所以正向边访问后,反向边也要标记
61 v = p -> v;
62 tmp = 0 ;
63 if ( ! dfn[v]){
64 par[v] = p -> op; // 由于dfs作用是建一棵深搜树,所以每个点的前边是是唯一的
65 tmp = dfs(v);
66 low[u] = Min(low[u], low[v]);
67 } else {
68 low[u] = Min(low[u], dfn[v]);
69 }
70 if (tmp){ // 如果v点可以到达终点,且u,v为割边的话,一定是0到N-1的必经之路
71 if (dfn[u] < low[v])
72 stk[ ++ top] = p -> id;
73 ok = 1 ;
74 }
75 }
76 return ok;
77 }
78 void Solve(){
79 int i;
80 top = 0 ;
81 memset(dfn, 0 , sizeof (dfn));
82 dfs( 0 );
83 if (top == 0 ){
84 puts( " 0 " );
85 puts( "" );
86 return ;
87 }
88 qsort(stk + 1 , top, sizeof (stk[ 0 ]), cmp);
89 printf( " %d\n " , top);
90 for (i = 1 ; i < top; i ++ ) printf( " %d " , stk[i]);
91 printf( " %d\n " , stk[i]);
92 }
93 int main()
94 {
95 int a, b, i;
96 while (scanf( " %d%d " , & N, & M) != EOF){
97 idx = 0 ;
98 memset(link, 0 , sizeof (link));
99 for (i = 0 ; i < M; i ++ ){
100 scanf( " %d%d " , & a, & b);
101 Add(a, b, i); // 将边的标号也记录进去
102 }
103 Solve();
104 }
105 return 0 ;
106 }
107
2 #include < stdio.h >
3 #include < stdlib.h >
4 #include < string .h >
5 #define NN 10004
6 #define MM 200010
7 int idx; // 边数
8 int bCnt; // 连通分支个个数 branch cnt
9 int time; // 记录时间戳
10 int low[NN]; // Low(u)为u或u的子树中能通过非父子边追溯到的最早的节点,即DFS序号最小的节点
11 int dfn[NN]; // 深度优先标号
12 int bel[NN]; // bel[u] = k,节点u属于分支k
13 int mark[NN];
14 int stk[MM];
15 int N, M, top;
16 typedef struct node{
17 int v;
18 int vis;
19 int id;
20 struct node * nxt, * op;
21 }NODE;
22
23 NODE edg[MM];
24 NODE * link[NN];
25 NODE * par[NN]; // 记录到达顶点v的树边
26
27 int Min( int a, int b){
28 return a < b ? a : b;
29 }
30
31 int cmp( const void * a, const void * b){
32 return * ( int * )a - * ( int * )b;
33 }
34 void Add( int u, int v, int id){ // 加边
35 idx ++ ;
36 edg[idx].v = v;
37 edg[idx].vis = 0 ;
38 edg[idx].nxt = link[u];
39 link[u] = edg + idx;
40 edg[idx].op = edg + idx + 1 ; // 表示反向边
41 edg[idx].id = id;
42 idx ++ ;
43 edg[idx].v = u;
44 edg[idx].vis = 0 ;
45 edg[idx].nxt = link[v];
46 link[v] = edg + idx;
47 edg[idx].op = edg + idx - 1 ;
48 edg[idx].id = id;
49 }
50
51 int dfs( int u){ // 1表示可以到达点N - 1,0表示不可到达
52 int v, tmp;
53 dfn[u] = low[u] = ++ time;
54 int ok = 0 ;
55 if (u == N - 1 ){
56 ok = 1 ;
57 }
58 for (NODE * p = link[u]; p; p = p -> nxt){
59 if (p -> vis) continue ;
60 p -> vis = p -> op -> vis = 1 ; // 由于加的是双向边,所以正向边访问后,反向边也要标记
61 v = p -> v;
62 tmp = 0 ;
63 if ( ! dfn[v]){
64 par[v] = p -> op; // 由于dfs作用是建一棵深搜树,所以每个点的前边是是唯一的
65 tmp = dfs(v);
66 low[u] = Min(low[u], low[v]);
67 } else {
68 low[u] = Min(low[u], dfn[v]);
69 }
70 if (tmp){ // 如果v点可以到达终点,且u,v为割边的话,一定是0到N-1的必经之路
71 if (dfn[u] < low[v])
72 stk[ ++ top] = p -> id;
73 ok = 1 ;
74 }
75 }
76 return ok;
77 }
78 void Solve(){
79 int i;
80 top = 0 ;
81 memset(dfn, 0 , sizeof (dfn));
82 dfs( 0 );
83 if (top == 0 ){
84 puts( " 0 " );
85 puts( "" );
86 return ;
87 }
88 qsort(stk + 1 , top, sizeof (stk[ 0 ]), cmp);
89 printf( " %d\n " , top);
90 for (i = 1 ; i < top; i ++ ) printf( " %d " , stk[i]);
91 printf( " %d\n " , stk[i]);
92 }
93 int main()
94 {
95 int a, b, i;
96 while (scanf( " %d%d " , & N, & M) != EOF){
97 idx = 0 ;
98 memset(link, 0 , sizeof (link));
99 for (i = 0 ; i < M; i ++ ){
100 scanf( " %d%d " , & a, & b);
101 Add(a, b, i); // 将边的标号也记录进去
102 }
103 Solve();
104 }
105 return 0 ;
106 }
107