poj3694 Network 双连通分量、并查集

好题。边双联通分量。

题目给了一个连通的无向图。那么,双连通分量缩点后,就得到一棵树。
每个操作就是将树上两个点和一直到公共祖先的所有点缩成一个点。

用并查集可以巧妙地完成这个操作。每次找到两个点的最近公共祖先,将两点到该祖先路径上的点都合并到祖先的并查集里,并进行路径压缩。路径上的边有多少,就将桥的数目减少多少。很方便就统计出剩下的桥的数目了。

算法每条边最多遍历一次,这样,最差的复杂度就是O(m)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
using namespace std;
#define MM 411100
#define NN 202000

int n,m,i,a,b,q,set[NN],now,cas,tote,first[NN],next[MM],v[MM],fi[NN],fv[2*MM],fn[2*MM],fe;
int totb,totdfn,top,low[NN],dfn[NN],sta[NN],block[NN];
int vis[NN],deg[NN],fa[NN];

void init(){memset(first,-1,sizeof(first));tote=0;}

inline void addedge(int a,int b){
    tote++;next[tote]=first[a];first[a]=tote;v[tote]=b;
    tote++;next[tote]=first[b];first[b]=tote;v[tote]=a;
}

void tarjan(int u,int fa){
    dfn[u]=low[u]=++totdfn;
    sta[++top]=u;
    int e,tmp;
    for(e=first[u];e!=-1;e=next[e])if (v[e]!=fa){
       if (!dfn[v[e]]){
           tarjan(v[e],u);
           if (low[v[e]]<low[u]) low[u]=low[v[e]];
           if (dfn[u]<low[v[e]]) {
             ++totb;
             do{
                tmp=sta[top--];
                block[tmp]=totb;
             }while(tmp!=v[e]);
           }
       }
       else if (dfn[v[e]]<low[u]) low[u]=dfn[v[e]];
    }
}
             

void bcc(){
    totb=totdfn=top=0;
    memset(dfn,0,sizeof(dfn));
    tarjan(1,-1);
    int tmp;
    if (top>0){
       ++totb;
       while(1){
          if (top==0) break;
          tmp=sta[top--];
          block[tmp]=totb;
       }
    }
}

inline void addedge2(int a,int b){
    fe++;fn[fe]=fi[a];fi[a]=fe;fv[fe]=b;
    fe++;fn[fe]=fi[b];fi[b]=fe;fv[fe]=a;
}

void dfs(int u,int f,int d){
    fa[u]=f;
    deg[u]=d;
    int e;
    vis[u]=1;
    for(e=fi[u];e!=-1;e=fn[e])if (!vis[fv[e]]){
        dfs(fv[e],u,d+1);
    }
}
        

void rebuild_graph(){
    memset(fi,-1,sizeof(fi));
    fe=0;
    int u,i,e;
    for(u=1;u<=n;u++){
      for(e=first[u];e!=-1;e=next[e]){
         addedge2(block[u],block[v[e]]);
      }
    }
    memset(vis,0,sizeof(vis));
    dfs(1,-1,1);
}

inline int find(int x){return x==set[x]?x:set[x]=find(set[x]);}

void work(int a,int b){
    a=block[a];b=block[b];
    int lca,ta=find(a),tb=find(b),cnt;
    while(ta!=tb){
       if (deg[ta]>deg[tb]) ta=fa[ta];
       else if (deg[tb]>deg[ta]) tb=fa[tb];
       else {ta=fa[ta];tb=fa[tb];}
       ta=find(ta);tb=find(tb);
    }
    lca=ta;
    ta=find(a);tb=find(b);
    cnt=0;
    while(ta!=lca){
       set[ta]=lca;
       ta=fa[ta];
       ta=find(ta);
       cnt++;
    }
    while(tb!=lca){
       set[tb]=lca;
       tb=fa[tb];
       tb=find(tb);
       cnt++;
    }
    now-=cnt;
}

struct Edge{int a,b;}edge[MM];

int eq(Edge x,Edge y){if (x.a!=y.a||x.b!=y.b) return 0;return 1;}

inline bool cmp(Edge x,Edge y){return x.a==y.a?x.b<y.b:x.a<y.a;}
          
int main(){
    //freopen("3694in.txt","r",stdin);
    cas=0;
    while(scanf("%d%d",&n,&m)&&(n||m)){
       init();
       edge[0].a=edge[0].b=0;
       for(i=1;i<=m;i++){
         scanf("%d%d",&a,&b);
         edge[i].a=min(a,b);edge[i].b=max(a,b);
         
       }
       sort(edge+1,edge+m+1,cmp);
       for(i=1;i<=m;i++)
         if (eq(edge[i],edge[i-1])==0)  addedge(edge[i].a,edge[i].b);
         
       bcc();
       rebuild_graph();
       
       for(i=1;i<=n;i++){set[i]=i;}
       now=totb-1;
       for(i=1;i<=m;i++) if (eq(edge[i],edge[i-1])) work(edge[i].a,edge[i].b);
          
       scanf("%d",&q);
       printf("Case %d:\n",++cas);
       for(i=1;i<=q;i++){
         scanf("%d%d",&a,&b);
         work(a,b);
         printf("%d\n",now);
       }
       printf("\n");
    }
    return 0;
}
       
          


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值