一道好题啊,加深了我对lca的理解。
/* *State: POJ3694 Accepted 6924K 407MS C++ 2466B *题目大意: * 给一个无向图,该图只有一个连通分量。然后查询q次,q < 1000, * 求每次查询就增加一条边,求剩余桥的个数。 *解题思路: * 求出搜索树的时间戳dfn,发现两点的lca的时间戳大于左子树,小于右子树。 * 之后可以通过合并连通分量的方式来计算剩余的桥的个数。合并连通分量的 * 方法,发现用并查集来合并,要方便得多啊,很容易合并掉两个大块。所以 * 就用并查集来合并连通分量了。 *解题感想; * 原来lca还可以这么个玩法。 */
用并查集实现加边过程:
View Code
#include <iostream> #include <vector> #include <cmath> #include <cstring> #include <cstdio> #include <vector> using namespace std; const int MAXN = 100005; const int MAXE = 200005; typedef struct _node { int v, next; }N; N edge[2 * MAXE]; int dfn[MAXN], low[MAXN]; int step, head[MAXN], cntEdge; int bridgeNum, pre[MAXN]; typedef struct _uqnode { int p; }U; U uqSet[MAXN]; void init() { step = cntEdge = bridgeNum = 0; for(int i = 0; i < MAXN; i++) { uqSet[i].p = i; head[i] = -1; dfn[i] = low[i] = -1; } } int findSet(int x) { if(x != uqSet[x].p) uqSet[x].p = findSet(uqSet[x].p); return uqSet[x].p; } bool Union(int x, int y) { int a = findSet(x); int b = findSet(y); if(a == b) return false; uqSet[b].p = a; return true; } //题目好像没有提到重边 void addEdge(int u, int v) { edge[cntEdge].v = v; edge[cntEdge].next = head[u]; head[u] = cntEdge++; edge[cntEdge].v = u; edge[cntEdge].next = head[v]; head[v] = cntEdge++; } void tarjan_scc(int n, int father) { dfn[n] = low[n] = ++step; int flag = 0; for(int f = head[n]; f != -1; f = edge[f].next) { int son = edge[f].v; if(son == father && !flag) { flag = 1; continue; } if(dfn[son] == -1) { pre[son] = n; tarjan_scc(son, n); low[n] = min(low[n], low[son]); if(low[son] > dfn[n]) { bridgeNum++; } else Union(n, son); } else low[n] = min(low[n], dfn[son]); } } int lca(int u, int v) { if(findSet(u) == findSet(v)) return bridgeNum; if(dfn[u] > dfn[v]) { u = u ^ v; v = u ^ v; u = u ^ v; } while(dfn[u] < dfn[v]) { if(Union(pre[v], v)) bridgeNum--; v = pre[v]; } while(u != v) { if(Union(u, pre[u])) bridgeNum--; u = pre[u]; } return bridgeNum; } int main(void) { #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); #endif int n, m, cas_c = 1; while(scanf("%d %d", &n, &m), n || m) { init(); int u, v; for(int i = 0; i < m; i++) { scanf("%d %d", &u, &v); addEdge(u, v); } pre[1] = 1; tarjan_scc(1, 1); int q; scanf("%d", &q); printf("Case %d:\n", cas_c++); for(int i = 0; i < q; i++) { scanf("%d %d", &u, &v); //查询 printf("%d\n", lca(u, v)); } } return 0; }
用数组实现加边过程:
View Code
#include <iostream> #include <vector> #include <cmath> #include <cstring> #include <cstdio> #include <vector> using namespace std; const int MAXN = 100005; const int MAXE = 200005; typedef struct _node { int v, next; }N; N edge[2 * MAXE]; int dfn[MAXN], low[MAXN]; int step, head[MAXN], cntEdge; int bridgeNum, pre[MAXN]; int bridge[MAXN], myS[MAXN], top, id[MAXN], scc; typedef struct _uqnode { int p; }U; U uqSet[MAXN]; void init() { top = 0; scc = 1; step = cntEdge = bridgeNum = 0; for(int i = 0; i < MAXN; i++) { id[i] = -1; bridge[i] = -1; uqSet[i].p = i; head[i] = -1; dfn[i] = low[i] = -1; } } //题目好像没有提到重边 void addEdge(int u, int v) { edge[cntEdge].v = v; edge[cntEdge].next = head[u]; head[u] = cntEdge++; edge[cntEdge].v = u; edge[cntEdge].next = head[v]; head[v] = cntEdge++; } void tarjan_scc(int n, int father) { dfn[n] = low[n] = ++step; int flag = 0; myS[top++] = n; for(int f = head[n]; f != -1; f = edge[f].next) { int son = edge[f].v; if(son == father && !flag) { flag = 1; continue; } if(dfn[son] == -1) { pre[son] = n; tarjan_scc(son, n); low[n] = min(low[n], low[son]); if(low[son] > dfn[n]) { bridgeNum++; bridge[son] = 1; } } else low[n] = min(low[n], dfn[son]); } if(low[n] == dfn[n]) { int tmp; do { tmp = myS[--top]; id[tmp] = scc; }while(top != 0 && tmp != n); scc++; } } int lca(int u, int v) { if(id[u] == id[v]) return bridgeNum; if(dfn[u] > dfn[v]) { u = u ^ v; v = u ^ v; u = u ^ v; } while(dfn[u] < dfn[v]) { if(bridge[v] == 1) { bridgeNum--; bridge[v] = -1; } v = pre[v]; } while(u != v) { if(bridge[u] == 1) { bridgeNum--; bridge[u] = -1; } u = pre[u]; } return bridgeNum; } int main(void) { #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); #endif int n, m, cas_c = 1; while(scanf("%d %d", &n, &m), n || m) { init(); int u, v; for(int i = 0; i < m; i++) { scanf("%d %d", &u, &v); addEdge(u, v); } pre[1] = 1; tarjan_scc(1, 1); int q; scanf("%d", &q); printf("Case %d:\n", cas_c++); for(int i = 0; i < q; i++) { scanf("%d %d", &u, &v); //查询 printf("%d\n", lca(u, v)); } } return 0; }