题目链接:Click here~~
题意:
n 个点 m 条边的无向图,之后有若干次操作,问每次添加一条边后,图上还剩多少个桥边(操作是累积的)。
解题思路:
先将无向图的 边-双连通分量 缩点,缩点后重新建图,则变为一颗树,树的每条边就可以看做桥边。不妨设每次添加的边为 <u,v> 。
1> 如果之前 u,v 在同一个双连通分量里,则显然不会对结果产生影响。
2> 如果不在,相当于在树中选取两点,连了一条边,从而形成一个环,环中的点会变成一个双连通分量。对结果产生的影响取决于环上的边数。
不难发现,这个环的路径就是: u -> LCA(u,v) -> v 。
如果操作不累积,容易想到重新建图后先转化成一棵树,然后预处理出每个点的深度,离线预处理出两两的 LCA,影响就是2*dep[lca] - dep[u] - dep[v]。
但此题是操作累积的,就不能这样做了。搜了题解,大概懂别人的思路了。
首先,找 LCA(u,v) 相当于 u,v 两个点一起向上爬,直到爬到同一个点 w 即为这两点的 LCA,统计共爬了多少边。我们把这种做法叫做 climb 吧。
我们来考虑两个点爬到 LCA 后,会对之后的操作产生什么影响。
之后的点如果再次爬之前爬过的路径,不会再对结果产生影响了。
既然爬之前爬过的路径这么费力不讨好,是不是能够想到一个好的方法,让它一次登顶?
于是这点就可以很巧妙的和并查集的路径压缩联系到一起。大家看代码体会吧。
#include <stack> #include <vector> #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; template<int N,int M> struct Graph{ int top; struct Vertex{ int head; }V[N]; struct Edge{ int v,next; }E[M]; void init(){ memset(V,-1,sizeof(V)); top = 0; } void add_edge(int u,int v){ E[top].v = v; E[top].next = V[u].head; V[u].head = top++; } }; const int N = 1e5 + 5; Graph<N,N*4> g,gg; int dfn[N],low[N],belong[N]; int bccId,dfsId; bool cutP[N],cutE[N*4]; vector<int> bcc[N]; void dfs(int pre,int u) { dfn[u] = low[u] = ++dfsId; int child = 0; for(int i=g.V[u].head;~i;i=g.E[i].next){ int v = g.E[i].v; if(v == pre || dfn[v] > dfn[u]) continue; if(!dfn[v]){ child++; dfs(u,v); low[u] = min(low[u],low[v]); if(pre != -1 && low[v] >= dfn[u] || pre == -1 && child > 1) cutP[u] = true; } else low[u] = min(low[u],dfn[v]); if(low[v] > dfn[u]){ cutE[i] = true; } } } void get_bcc_p(int n) { dfsId = bccId = 0; memset(dfn,0,sizeof(dfn)); memset(cutP,false,sizeof(cutP)); memset(cutE,false,sizeof(cutE)); memset(belong,-1,sizeof(belong)); for(int i=1;i<=n;i++) if(!dfn[i]) dfs(-1,i); } bool vis[N]; void dfs2(int u) { vis[u] = true; belong[u] = bccId; bcc[bccId].push_back(u); for(int i=g.V[u].head;~i;i=g.E[i].next){ int v = g.E[i].v; if(!vis[v] && !cutE[i] && !cutE[i^1]) dfs2(v); } } void get_bcc_e(int n) { bccId = 0; memset(vis,false,sizeof(vis)); for(int i=1;i<=n;i++){ if(vis[i]) continue; bcc[++bccId].clear(); dfs2(i); } } int father[N],dep[N]; void dfss(int u,int depth) { vis[u] = true; dep[u] = depth; for(int i=gg.V[u].head;~i;i=gg.E[i].next) { int v = gg.E[i].v; if(!vis[v]){ father[v] = u; dfss(v,depth+1); } } } void rebuild(int n) { for(int u=1;u<=n;u++) for(int i=g.V[u].head;~i;i=g.E[i].next){ int v = g.E[i].v; if(belong[u] != belong[v]) gg.add_edge(belong[u],belong[v]), gg.add_edge(belong[v],belong[u]); } memset(vis,false,sizeof(vis)); father[1] = 0; dfss(1,0); } namespace ufSet { //const int N = 1e5 + 5; int pre[N]; void init(){ memset(pre,-1,sizeof(pre)); } int root(int x){ return pre[x] == -1 ? x : pre[x] = root(pre[x]); } bool gather(int a,int b){ int r1 = root(a); int r2 = root(b); if(r1 == r2) return false; else pre[r1] = r2; return true; } } using namespace ufSet; int climb(int a,int b){ int ret = 0; while(1){ a = root(a); b = root(b); if(a == b) break; if(dep[a] > dep[b]) gather(a,father[a]); else gather(b,father[b]); ++ret; } return ret; } void debug() { for(int i=1;i<=bccId;i++) for(int j=0;j<(int)bcc[bccId].size();j++) printf("%d->%d%c",bcc[i][j],i,j==(int)bcc[bccId].size()-1?'\n':' '); } int main() { int n,m,Q,ncase = 0; while(scanf("%d%d",&n,&m),n||m) { g.init(); gg.init(); ufSet::init(); while(m--) { int u,v; scanf("%d%d",&u,&v); g.add_edge(u,v); g.add_edge(v,u); } get_bcc_p(n); get_bcc_e(n); rebuild(n); scanf("%d",&Q); int ans = bccId - 1; printf("Case %d:\n",++ncase); while(Q--) { int u,v; scanf("%d%d",&u,&v); ans -= climb(belong[u],belong[v]); printf("%d\n",ans); } puts(""); } return 0; }