POJ_3694 Network Tarjin + LCA + 并查集

http://poj.org/problem?id=3694

题意:给定一个有N个点,M条边的无向图(图原本连通),N<=10000 ,M<=20000,现在要求将Q条边逐一加入到无向图中去,要求对于每次加入边之后,输出图中的桥的条数。

思路:先对原无向图缩点,初始时候,桥的条数为:cnt -1 , 其中cnt为图中连通分量的个数,每次增加一条边的时候,我们可以先找出两个顶点的LCA,然后沿着树边往根移动,将这个圈又进行缩点,此时就可以在O(N)的时候内求出此时的桥。因为只需要求两个点之间的LCA,所有可以直接从两个结点往上找,知道找到相同的结点为止。

代码:

#include<stdio.h>
#include<string.h>
const int MAXN = 100010 ;
const int MAXM = 200010 ;
int N , M , Q ;
int Gnext[MAXM*2] ;
int Gv[MAXM*2] ;
int Gr[MAXN], Gc ;

int gnext[MAXM*2] ;
int gv[MAXM*2] ;
int gr[MAXN],gc ;
int ans ;
void init(){
    memset(Gr, -1 ,sizeof(Gr));
    memset(gr, -1, sizeof(gr)) ;
    Gc = 0 ; gc = 0 ;
}
void add(int a,int b){
    Gv[Gc] = b ;
    Gnext[Gc] = Gr[a] ;
    Gr[a] = Gc++ ;
}
int dfn[MAXN] , low[MAXN] , col[MAXN] ,st[MAXN];
int idx, top, cnt ;

void tarjin(int x,int fa){
    low[x] = dfn[x] = ++idx ;
    st[top++] = x ;
    bool f = 1 ;
    for(int i=Gr[x] ;i!=-1;i=Gnext[i]){
        int v = Gv[i] ;
        if(v==fa && f){
            f = 0 ;     continue ;
        }
        if( dfn[v] == -1){
            tarjin(v,x);
            if( low[v] < low[x])    low[x] = low[v] ;
            if( low[v] > dfn[x] ){
                for( st[top]=-1 ;st[top]!=v;){
                    top-- ;
                    col[ st[top] ] = cnt ;
                }
                cnt++ ;
            }
        }
        else if(dfn[v] < low[x])
            low[x] = dfn[v] ;
    }
}
void add1(int a, int b){
    gv[gc] = b ;
    gnext[gc] = gr[a] ;
    gr[a] = gc++ ;
}
int p[MAXN] ;

void solve(){
    top = idx = 0 ;
    cnt = 1 ;
    memset(dfn, -1, sizeof(dfn)) ;
    memset(col,0, sizeof(col)) ;
    for(int i=1;i<=N;i++){
        if( dfn[i] == -1){
            tarjin(i,-1);
        }
    }
    for(int i=1;i<=N;i++){
        for(int j=Gr[i] ;j!=-1;j=Gnext[j]){
            int u = Gv[j] ;
            if( col[i] != col[u] ){
                add1( col[i] , col[u] );
            }
        }
    }
    for(int i=0;i<cnt;i++){
        p[i] = i ;
    }
}
int pre[MAXN] ;
bool vis[MAXN] ;

void build(int u,int fa){
    for(int i=gr[u];i!=-1;i=gnext[i]){
        int v = gv[i] ;
        if(v == fa) continue ;
        pre[v] = u ;
        build(v,u) ;
    }
}
void deal(int a, int b){
    memset(vis, 0 ,sizeof(vis));
    int aa = a ;
    vis[aa] = 1 ;
    int ac ,bc ;
    ac = bc = 0 ;
    while(pre[aa] != -1){
        vis[ pre[aa] ]  = 1 ;
        aa = pre[aa] ;
    }
    int bb = b ;
    int ancer = -1;
    if( vis[bb] == 1) ancer = bb ;
    else{
        while( pre[bb]!=-1 && !vis[ pre[bb] ]){
            bb = pre[bb] ;
        }
        if( pre[bb]!=-1 ){
            ancer = pre[bb] ;
        }
    }
    if(ancer == -1) return ;
    aa = a ;
    while( aa != ancer){
        ac ++ ;
        aa = pre[aa] ;
    }
    bb = b ;
    while( bb != ancer){
        bc ++ ;
        bb = pre[bb] ;
    }
    ans = ans - ac - bc ;

    for(aa=a;aa!=ancer;aa=pre[aa]){
        p[aa] = ancer ;
        for(int j=gr[aa] ;j!=-1;j=gnext[j]){
            int v = gv[j] ;
            if(v == pre[aa])    continue ;
            pre[v] = ancer ;
        }
    }
    for(bb=b;bb!=ancer;bb=pre[bb]){
        p[bb] = ancer ;
        for(int j=gr[bb] ;j!=-1;j=gnext[j]){
            int v = gv[j] ;
            if(v == pre[bb])    continue ;
            pre[v] = ancer ;
        }
    }
}
int find(int a){
    if(a != p[a]){
        p[a] = find( p[a] ) ;
    }
    return p[a] ;
}

int main(){
    int a, b ;
    int cas = 0 ;
    while(scanf("%d%d",&N,&M) == 2){
        if(0==N && M==0)    break ;
        ++cas ;
        init() ;
        for(int i=1;i<=M;i++){
            scanf("%d%d",&a,&b);
            add(a,b);   add(b,a);
        }
        solve();
        memset(pre, -1, sizeof(pre));
        build(0,-1);
        scanf("%d",&Q);
        ans = cnt - 1;
        printf("Case %d:\n",cas);
        for(int i=1;i<=Q;i++){
            scanf("%d%d",&a,&b);
            int fa = find( col[a] ) ;
            int fb = find( col[b] ) ;
            if( fa == fb ){
                printf("%d\n",ans);
            }
            else{
                deal( fa,fb );
                printf("%d\n",ans);
            }
        }
        printf("\n");
    }
    return 0;
}



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值