poj3694 network(边双联通分量+lca+并查集)
题目大意
有一个由n个点和m条边组成的无向联通图。
现在有Q个操作,每次操作可以在点x,y之间连一条边。
问你每次操作后有多少个多少个桥(即删掉后就会使图不联通的边)。
解题思路
根据边双联通的定义,我们知道将边双联通分量缩点后的图,其中的边即为桥。
我们将这个图缩点,就变成了一棵树。
而每次在两个不同的边双联通分量x,y之间加边后,就出现了一个包含x,y的环,其中原先这颗树上x,y的树上最短路径就不在是边。
所以对于每个x,y,我们用倍增求出它的最近公共祖先z,其中包含的桥的数量为x的深度+y的深度-2*z的深度。
但是需要注意的是有些边会被算过多次,我们可以将这些边标记一下,然后每次直接dfs走一遍x和y到z的路径进行统计。
而我这里用了并查集进行优化,分别跑一遍x到z和y到z的路径,将每个点都指向父节点,然后进行路径合并,下次再次统计的时候会直接跳过。
代码如下:
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <vector> #include <cmath> #define rep(x, l, r) for(int x = l; x <= r; x++) #define repd(x, r, l) for(int x = r; x >= l; x--) #define clr(x, y) memset(x, y, sizeof(x)) #define all(x) x.begin(), x.end() #define pb push_back #define mp make_pair #define MAXN 300005 #define MAXM 1200005 #define fi first #define se second #define SZ(x) ((int)x.size()) using namespace std; typedef long long LL; typedef vector<int> vi; typedef pair<int, int> pii; const int INF = 0x3f3f3f3f; const int p = 10000007; int lowbit(int x){ return x & -x; } int fast_power(int a, int b){ int x; for(x = 1; b; b >>= 1){ if(b & 1) x = 1ll * x * a % p; a = 1ll * a * a % p; } return x; } int n, t, cnt, cnt2, num, dcc; int head[MAXN], nxt[MAXM], to[MAXM]; int head2[MAXN], nxt2[MAXM], to2[MAXM]; int dfn[MAXN], low[MAXN], c[MAXN]; bool bge[MAXN]; int deep[MAXN], fa[MAXN][25], fa2[MAXN]; void init(){ cnt = 0; clr(head, -1); cnt2 = 0; clr(head2, -1); clr(dfn, 0); clr(low, 0); clr(bge, 0); clr(c, 0); clr(fa, 0); clr(deep, 0); dcc = 0; num = 0; } void addedge(int u, int v){ nxt[cnt] = head[u]; head[u] = cnt; to[cnt] = v; cnt++; } void tarjan(int u, int in_edge){ dfn[u] = low[u] = ++num; for(int e = head[u]; e != -1; e = nxt[e]){ int v = to[e]; if(!dfn[v]){ tarjan(v, e); low[u] = min(low[u], low[v]); if(dfn[u] < low[v]) bge[e] = bge[e ^ 1] = 1; } else if(e != (in_edge ^ 1)) low[u] = min(low[u], dfn[v]); } } void dfs(int u){ c[u] = dcc; for(int e = head[u]; e != -1; e = nxt[e]){ int v = to[e]; if(c[v] || bge[e]) continue; dfs(v); } } void addedge2(int u, int v){ nxt2[cnt2] = head2[u]; head2[u] = cnt2; to2[cnt2] = v; cnt2++; } void dfs2(int u){ for(int e = head2[u]; e != -1; e = nxt2[e]){ int v = to2[e]; if(!deep[v]){ fa[v][0] = u; deep[v] = deep[u] + 1; dfs2(v); } } } void init2(){ rep(j, 1, t) rep(i, 1, n) fa[i][j] = fa[fa[i][j - 1]][j - 1]; } int lca(int x, int y){ if(deep[x] < deep[y]) swap(x, y); repd(i, t, 0) if(deep[fa[x][i]] >= deep[y]) x = fa[x][i]; if(x == y) return x; repd(i, t, 0) if(fa[x][i] != fa[y][i]){ x = fa[x][i]; y = fa[y][i]; } return fa[x][0]; } int find(int x){ if (x == fa2[x]) return x; return fa2[x] = find(fa2[x]); } int main(){ int m; int times = 0; while(~scanf("%d%d", &n, &m) && n && m){ printf("Case %d:\n", ++times); init(); t = log2(n) + 1; rep(i, 1, m){ int u, v; scanf("%d%d", &u, &v); addedge(u, v); addedge(v, u); } rep(i, 1, n) if(!dfn[i]) tarjan(1, -1); rep(i, 1, n){ if(!c[i]){ ++dcc; dfs(i); } } rep(i, 0, cnt - 1){ int u = to[i ^ 1], v = to[i]; if(c[u] == c[v]) continue; addedge2(c[u], c[v]); } deep[1] = 1; dfs2(1); init2(); int k; scanf("%d", &k); int ans = dcc - 1; rep(i, 1, dcc) fa2[i] = i; rep(i, 1, k){ int u, v; scanf("%d%d", &u, &v); int x = c[u], y = c[v], father = lca(x, y); x = find(x); while(deep[x] > deep[father]){ fa2[x] = fa[x][0]; ans--; x = find(x); } y = find(y); while(deep[y] > deep[father]){ fa2[y] = fa[y][0]; ans--; y = find(y); } printf("%d\n", ans); } puts(""); } return 0; }