POJ3694 Network —— 边双联通分量 + 缩点 + LCA + 并查集
题目链接:https://vjudge.net/problem/POJ-3694
A network administrator manages a large network. The network consists of N computers and M links between pairs of computers. Any pair of computers are connected directly or indirectly by successive links, so data can be transformed between any two computers. The administrator finds that some links are vital to the network, because failure of any one of them can cause that data can't be transformed between some computers. He call such a link a bridge. He is planning to add some new links one by one to eliminate all bridges.
You are to help the administrator by reporting the number of bridges in the network after each new link is added.
Input
The input consists of multiple test cases. Each test case starts with a line containing two integers N(1 ≤ N ≤ 100,000) and M(N - 1 ≤ M ≤ 200,000).
Each of the following M lines contains two integers A and B ( 1≤ A ≠ B ≤ N), which indicates a link between computer A and B. Computers are numbered from 1 to N. It is guaranteed that any two computers are connected in the initial network.
The next line contains a single integer Q ( 1 ≤ Q ≤ 1,000), which is the number of new links the administrator plans to add to the network one by one.
The i-th line of the following Q lines contains two integer A and B (1 ≤ A ≠ B ≤ N), which is the i-th added new link connecting computer A and B.
The last test case is followed by a line containing two zeros.
Output
For each test case, print a line containing the test case number( beginning with 1) and Q lines, the i-th of which contains a integer indicating the number of bridges in the network after the first i new links are added. Print a blank line after the output for each test case.
Sample Input
3 2 1 2 2 3 2 1 2 1 3 4 4 1 2 2 1 2 3 1 4 2 1 2 3 4 0 0
Sample Output
Case 1: 1 0 Case 2: 2 0
题解:
1.利用Tarjan算法,求出每个边双联通分量,并且记录每个点属于哪一个分量。
2.将每一个边双联通分量缩成一个点,最终得到一棵树。而我们想要得到一棵有根树,怎么办?其实在执行Tarjan算法的时候,就已经形成了一个有根树。所以我们只需要在Tarjan算法的基础上,再记录每一个点的父节点以及深度就可以了。
3.每次询问的时候,如果两个点在同一个分量中,那么他们的相连不会减少桥的个数。如果两个点在不同的分量中,那么u->LCA(u,v)和v->LCA(u,v)上路径上的桥,都可以减少,路径上的点都可以缩成一个点,即合并成一个分量。
对于缩点的处理:
方法一:对于一个分量,可以设置一个点为实点,其余的点为虚点。实点即代表着这个分量的所有信息,虚点虽然属于这个分量的点,但是却对他视而不见。我们要做的,就是在这个分量里选择一个点,去代表整个分量。
方法二:同样地,我们也需要为每一个分量选出一个代表,以表示这个分量。与方法一的“视而不见”不同的是,方法二对每一个点都设置了一个归属集合,即表示这个点属于哪一个集合。由于在处理的过程中,一个集合可能又会被另一个集合所包含,所以我们可以利用并查集的路径压缩,很快地找到一个点的最终所属集合。
方法一:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 #include <algorithm> 6 #include <vector> 7 #include <queue> 8 #include <stack> 9 #include <map> 10 #include <string> 11 #include <set> 12 #define ms(a,b) memset((a),(b),sizeof((a))) 13 using namespace std; 14 typedef long long LL; 15 const double EPS = 1e-8; 16 const int INF = 2e9; 17 const LL LNF = 2e18; 18 const int MAXN = 1e5+10; 19 20 struct Edge 21 { 22 int to, next; 23 }edge[MAXN*8]; 24 int tot, head[MAXN]; 25 26 int index, dfn[MAXN], low[MAXN]; 27 int isbridge[MAXN], sum_bridge; 28 int fa[MAXN], depth[MAXN]; 29 30 void addedge(int u, int v) 31 { 32 edge[tot].to = v; 33 edge[tot].next = head[u]; 34 head[u] = tot++; 35 } 36 37 void Tarjan(int u, int pre) 38 { 39 dfn[u] = low[u] = ++index; 40 depth[u] = depth[pre] + 1; //记录深度 41 fa[u] = pre; //记录父亲结点 42 for(int i = head[u]; i!=-1; i = edge[i].next) 43 { 44 int v = edge[i].to; 45 if(v==pre) continue; 46 if(!dfn[v]) 47 { 48 Tarjan(v, u); 49 low[u] = min(low[u], low[v]); 50 if(low[v]>dfn[u]) //isbridge[v]表示在树中,以v为儿子结点的边是否为桥 51 isbridge[v] = 1, sum_bridge++; 52 } 53 else 54 low[u] = min(low[u], dfn[v]); 55 } 56 } 57 58 void LCA(int u, int v) 59 { 60 if(depth[u]<depth[v]) swap(u, v); 61 while(depth[u]>depth[v]) //深度大的先往上爬。遇到桥,就把它删去。 62 { 63 if(isbridge[u]) sum_bridge--, isbridge[u] = 0; 64 u = fa[u]; 65 } 66 while(u!=v) //当深度一样时,一起爬。遇到桥,就把它删去。 67 { 68 if(isbridge[u]) sum_bridge--, isbridge[u] = 0; 69 u = fa[u]; 70 if(isbridge[v]) sum_bridge--, isbridge[v] = 0; 71 v = fa[v]; 72 } 73 } 74 75 void init() 76 { 77 tot = 0; 78 memset(head, -1, sizeof(head)); 79 80 index = 0; 81 memset(dfn, 0, sizeof(dfn)); 82 memset(low, 0, sizeof(low)); 83 memset(isbridge, 0, sizeof(isbridge)); 84 85 sum_bridge = 0; 86 } 87 88 int main() 89 { 90 int n, m, kase = 0; 91 while(scanf("%d%d", &n, &m) && (n||m) ) 92 { 93 init(); 94 for(int i = 1; i<=m; i++) 95 { 96 int u, v; 97 scanf("%d%d", &u, &v); 98 addedge(u, v); 99 addedge(v, u); 100 } 101 102 depth[1] = 0; 103 Tarjan(1, 1); 104 int q, a, b; 105 scanf("%d", &q); 106 printf("Case %d:\n", ++kase); 107 while(q--) 108 { 109 scanf("%d%d", &a, &b); 110 LCA(a, b); 111 printf("%d\n", sum_bridge); 112 } 113 printf("\n"); 114 } 115 }
方法二:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 #include <algorithm> 6 #include <vector> 7 #include <queue> 8 #include <stack> 9 #include <map> 10 #include <string> 11 #include <set> 12 #define ms(a,b) memset((a),(b),sizeof((a))) 13 using namespace std; 14 typedef long long LL; 15 const double EPS = 1e-8; 16 const int INF = 2e9; 17 const LL LNF = 2e18; 18 const int MAXN = 1e6+10; 19 20 struct Edge 21 { 22 int to, next; 23 }edge[MAXN], edge0[MAXN]; //edge为初始图, edge0为重建图 24 int tot, head[MAXN], tot0, head0[MAXN]; 25 26 int index, dfn[MAXN], low[MAXN]; 27 int top, Stack[MAXN], instack[MAXN]; 28 int belong[MAXN]; 29 int fa[MAXN], depth[MAXN]; //fa用于重建图时记录当前节点的父亲节点,depth记录当前节点的深度 30 int sum_bridge; 31 32 //找到x最终所属的结合 33 int find(int x) { return belong[x]==x?x:belong[x]=find(belong[x]); } 34 35 void addedge(int u, int v, Edge edge[], int head[], int &tot) 36 { 37 edge[tot].to = v; 38 edge[tot].next = head[u]; 39 head[u] = tot++; 40 } 41 42 void Tarjan(int u, int pre) 43 { 44 dfn[u] = low[u] = ++index; 45 Stack[top++] = u; 46 instack[u] = true; 47 for(int i = head[u]; i!=-1; i = edge[i].next) 48 { 49 int v = edge[i].to; 50 if(v==pre) continue; 51 if(!dfn[v]) 52 { 53 Tarjan(v, u); 54 low[u] = min(low[u], low[v]); 55 if(low[v]>dfn[u]) sum_bridge++; 56 } 57 else if(instack[v]) 58 low[u] = min(low[u], dfn[v]); 59 } 60 61 if(dfn[u]==low[u]) 62 { 63 int v; 64 do 65 { 66 v = Stack[--top]; 67 instack[v] = false; 68 belong[v] = u; //把集合的编号设为联通分量的第一个点 69 }while(v!=u); 70 } 71 } 72 73 void build(int u, int pre) 74 { 75 fa[u] = pre; //记录父亲节点 76 depth[u] = depth[pre] + 1; //记录深度 77 for(int i = head0[u]; i!=-1; i=edge0[i].next) 78 if(edge0[i].to!=pre) //防止往回走 79 build(edge0[i].to, u); 80 } 81 82 83 int LCA(int u, int v) //左一步右一步地找LCA 84 { 85 if(u==v) return u; //因为两个结点一定有LCA, 所以一定有u==v的时候 86 87 //可能爬一步就爬了几个深度,因为中间的结点已经往上缩点了 88 if(depth[u]<depth[v]) swap(u, v); //深度大的往上爬 89 sum_bridge--; 90 int lca = LCA(find(fa[u]), v); 91 return belong[u] = lca; //找到了LCA,在沿路返回的时候把当前节点的所属集合置为LCA的所属集合 92 } 93 94 void init() 95 { 96 tot = tot0 = 0; 97 memset(head, -1, sizeof(head)); 98 memset(head0, -1, sizeof(head0)); 99 100 index = top = 0; 101 memset(dfn, 0, sizeof(dfn)); 102 memset(low, 0, sizeof(low)); 103 memset(instack, 0, sizeof(instack)); 104 105 sum_bridge = 0; 106 } 107 108 int main() 109 { 110 int n, m, kase = 0; 111 while(scanf("%d%d", &n, &m) && (n||m) ) 112 { 113 init(); 114 for(int i = 1; i<=m; i++) 115 { 116 int u, v; 117 scanf("%d%d", &u, &v); 118 addedge(u, v, edge, head, tot); 119 addedge(v, u, edge, head, tot); 120 } 121 122 Tarjan(1, 1); 123 for(int u = 1; u<=n; u++) //重建建图 124 for(int i = head[u]; i!=-1; i = edge[i].next) 125 { 126 int tmpu = find(u); 127 int tmpv = find(edge[i].to); 128 if(tmpu!=tmpv) 129 addedge(tmpu, tmpv, edge0, head0, tot0); 130 } 131 132 depth[find(1)] = 0; 133 build(find(1), find(1)); //把无根树转为有根树 134 135 int q, a, b; 136 scanf("%d", &q); 137 printf("Case %d:\n", ++kase); 138 while(q--) 139 { 140 scanf("%d%d", &a, &b); 141 LCA(find(a), find(b)); 142 printf("%d\n", sum_bridge); 143 } 144 printf("\n"); 145 } 146 }