POJ3694 Network(边双连通分量+缩点+LCA)

时间:2023-03-09 22:42:50
POJ3694 Network(边双连通分量+缩点+LCA)

题目大概是给一张图,动态加边动态求割边数。

本想着求出边双连通分量后缩点,然后构成的树用树链剖分+线段树去维护路径上的边数和。。好像好难写。。

看了别人的解法,这题有更简单的算法:

在任意两点添边,那么两点路径上的边就不是割边了,于是从两点往上走到其LCA,一边缩点一边统计消失的割边数。

这样的时间复杂度是保证的,因为最多就把所有点缩完而最多走的边数差不多就原图的边数。

具体实现,用Tarjan求出边双连通分量后缩点;缩点用并查集,要注意合并次序深度小的作深度大的点的根;最后就是对每个询问的两个点向上走到其LCA一边走一边更新。

另外,不必去维护缩点后的树,直接维护Tarjan算法构造的深度优先生成树就行了。

 #include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 111111
#define MAXM 444444
struct Edge{
int v,next;
bool flag;
}edge[MAXM];
int NE,head[MAXN];
void addEdge(int u,int v){
edge[NE].v=v; edge[NE].next=head[u]; edge[NE].flag=;
head[u]=NE++;
} int par[MAXN];
int Find(int a){
while(a!=par[a]){
par[a]=par[par[a]];
a=par[a];
}
return a;
}
void Union(int a,int b){
int pa=Find(a),pb=Find(b);
if(pa==pb) return;
par[pb]=pa;
} int fa[MAXN],dep[MAXN],cut;
int dn,dfn[MAXN],low[MAXN];
void dfs(int u){
dfn[u]=low[u]=++dn;
for(int i=head[u]; i!=-; i=edge[i].next){
if(edge[i].flag) continue;
int v=edge[i].v;
if(dfn[v]){
low[u]=min(low[u],dfn[v]);
continue;
}
fa[v]=u; dep[v]=dep[u]+;
edge[i].flag=edge[i^].flag=;
dfs(v);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u]) ++cut;
else Union(u,v);
}
} int lca(int u,int v){
int cnt=;
u=Find(u); v=Find(v);
while(u!=v){
if(dep[u]>dep[v]){
Union(fa[u],u); ++cnt;
u=Find(fa[u]);
}else if(dep[v]>dep[u]){
Union(fa[v],v); ++cnt;
v=Find(fa[v]);
}else{
Union(fa[u],u); Union(fa[v],v); cnt+=;
u=Find(fa[u]); v=Find(fa[v]);
}
}
return cnt;
}
int main(){
int n,m,a,b,t=;
while(~scanf("%d%d",&n,&m) && (n||m)){
NE=;
memset(head,-,sizeof(head));
while(m--){
scanf("%d%d",&a,&b);
addEdge(a,b); addEdge(b,a);
} for(int i=; i<=n; ++i) par[i]=i;
cut=dn=;
memset(dfn,,sizeof(dfn));
dfs(); printf("Case %d:\n",++t);
scanf("%d",&m);
while(m--){
scanf("%d%d",&a,&b);
cut-=lca(a,b);
printf("%d\n",cut);
}
putchar('\n');
}
return ;
}