POJ - 3694 Network (割边+LCA)
题意:给一张无向图,有M次加边的操作,每次操作之后输出割边的数目。
分析:显然,割边肯定出现在任意一棵生成树中,用数组f[u]记录点u在dfs树上的父亲节点,用这种方式就可以快速地找出dfs树上的任意一条边。在u,v之间加边后,原来的减去的割边肯定是u,v在dfs树上的最短路径中出现。那么每次操作之后,在求u,v两点lca的过程中,就可以判断减少了多少条割边。
但O(n)地求lca效率上并不好,仍有改进的空间。在求lca的过程中,如果一条边确定不是割边,那么之后也没有访问它的必要,所以可以将其向上合并。
并且在初始Tarjan求割边的过程中,就可将不是割边的边向上合并。
#include<iostream> #include<algorithm> #include<stdio.h> #include<cstring> using namespace std; const int maxn =1e5+5; struct Edge{ int to,next; }edges[maxn*4]; int pre[maxn],low[maxn],f[maxn],head[maxn],dfn,tot,top; bool judge[maxn]; void init() { dfn=tot=top=0; f[1]=1; memset(pre,0,sizeof(pre)); memset(low,0,sizeof(low)); memset(head,-1,sizeof(head)); memset(judge,0,sizeof(judge)); } int Find(int x){ return f[x]==x? x:f[x] =Find(f[x]); } void Union(int u,int v) { int fu= Find(u); int fv = Find(v); if(fu!=fv){ //后向前并 f[fv] =fu; } } void AddEdge(int u,int v) { edges[top].to =v; edges[top].next = head[u]; head[u] = top++; } void Tarjan(int u,int fa,int id) //id表示边的序号 { f[u]=fa; pre[u] = low[u]= ++dfn; int v; for(int i=head[u];i!=-1;i=edges[i].next){ v = edges[i].to; if(i==(id^1)) continue; //实际上是一条边 没有访问的必要 if(!pre[v]){ Tarjan(v,u,i); low[u] = min(low[u],low[v]); } else low[u]=min(low[u],pre[v]); if(low[v]>pre[u]){ //割边 tot++; judge[v]=true; } else Union(u,v); //缩点 } } void LCA(int u,int v) { if(pre[v]<pre[u]) swap(u,v); while(pre[v]>pre[u]){ if(judge[v]){tot--;judge[v]=false;} v = f[v]; } while(u!=v){ if(judge[u]){tot--;judge[u]=false;} if(judge[v]){tot--;judge[v]=false;} u = f[u]; v = f[v]; } } int main() { int N,M,u,v,tmp,Q; int T=1; while(~scanf("%d%d",&N,&M),N+M){ init(); for(int i=0;i<M;++i){ scanf("%d%d",&u,&v); AddEdge(u,v); AddEdge(v,u); } Tarjan(1,1,-1); printf("Case %d:\n",T++); scanf("%d",&Q); for(int i=0;i<Q;++i){ scanf("%d%d",&u,&v); //选取low较小的点作为终点 LCA(u,v); printf("%d\n",tot); } printf("\n"); } return 0; }
为了更好的明天