Network POJ-3694(tarjan)

题目链接:POJ-3694
**题意:**给出一张n个点m条边的无向连通图,q次操作,每次添加一条无向边,然后输出当前图的桥的数量。
数据范围: n &lt; = 1 0 5 , m &lt; = 2 ∗ 1 0 5 , q &lt; = 1 0 3 n&lt;=10^5,m&lt;=2*10^5,q&lt;=10^3 n<=105,m<=2105,q<=103
首先我们先跑一次tarjan将边双连通分量求出来,将这些连通分量缩为一个点,建一张新图,建出来的图一定是一颗树,此时桥的数量就是连通分量的个数-1.
考虑在x与y之间添加一条边,我们在缩点之后的树中添加,用belong[]来表示缩点后点x对应的连通分量编号,那么

  • 如果 b e l o n g [ x ] = = b e l o n g [ y ] belong[x]==belong[y] belong[x]==belong[y],此时是在连通分量内部添加边,故不会改变桥的数量。
  • 如果不一致的话,需要先求出 z = L C A ( b e l o n g [ x ] , b e l o n g [ y ] ) z=LCA(belong[x],belong[y]) z=LCA(belong[x],belong[y]),考虑将添边操作转化为在 b e l o n g [ x ] 与 z , b e l o n g [ y ] 与 z belong[x]与z,belong[y]与z belong[x]zbelong[y]z之间添边,那么这中间有多少条边,桥就会减少多少(因为他们构成了环)。那么可以从belong[x]开始不断的向上一步步的走向lca,将这中间没有被标记过的边标记出来,并且桥的数量减1,另一边也是同理,时间复杂度为 O ( n ∗ q ) O(n*q) O(nq)
  • 当然对于操作二还可以用并查集来优化,就是不断的将x合并到他的父亲中去,那么已经被标记过的边就不会再走,而是直接跳到没有标记过的边的父亲节点去:具体看下面代码。
//fre为并查集,father为x的直接父亲;
void updata(int x,int y){
    int lca=LCA(x,y);
    int fx,fy;
    while(1){
        fx=myfind(x);//并查集;
        fy=myfind(lca);
        if(fx==fy) break;
        fre[fx]=myfind(father[x]);
        --res;
        x=father[x];
    }
    while(1){
        fx=myfind(y);
        fy=myfind(lca);
        if(fx==fy) break;
        fre[fx]=myfind(father[y]);
        --res;
        y=father[y];
    }
}

好,总结一下:

  1. 先求割边,然后边双连通分量划分。
  2. 建新图(树),预处理lca一系列东西。
  3. 进行q次操作。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;

typedef long long ll;

const int maxn=1e5+7;

struct Edge{
    int v,w,next;
}edge[500009],edge1[500009];

int head[maxn],top;
int head1[maxn],top1;

void add(int u,int v,int w){
    edge[top].v=v;
    edge[top].w=w;
    edge[top].next=head[u];
    head[u]=top++;
}
void add1(int u,int v,int w){
    edge1[top1].v=v;
    edge1[top1].w=w;
    edge1[top1].next=head1[u];
    head1[u]=top1++;
}

void init(){
    top=0;
    memset(head,-1,sizeof(head));
}

void init1(){
    top1=0;
    memset(head1,-1,sizeof(head1));
}

bool bridge[500009];
int belong[maxn];
int cnt;
int dfn[maxn],low[maxn],num;

//求割边;
void tarjan(int u,int last){
    dfn[u]=low[u]=++num;
    int v;
    for(int i=head[u];i!=-1;i=edge[i].next){
        v=edge[i].v;
        if(!dfn[v]){
            tarjan(v,i);
            low[u]=min(low[u],low[v]);
            if(low[v]>dfn[u])
                bridge[i]=bridge[i^1]=1;
        }
        else if(last==-1||(last^1)!=i)//因为我的前向星是从0开始存储的;
            low[u]=min(low[u],dfn[v]);
    }
}

//连通分量划分;
void dfs(int u){
    belong[u]=cnt;
    for(int i=head[u];i!=-1;i=edge[i].next){
        if(belong[edge[i].v]||bridge[i]) continue;
        dfs(edge[i].v);
    }
}

int depth[maxn];
int fa[maxn][19];
int father[maxn];
int t=18;

//lca预处理;
queue<int> q;
void bfs(){
    memset(depth,0,sizeof(depth));
    memset(fa,0,sizeof(fa));
    int u,v;
    while(!q.empty()) q.pop();
    q.push(1);
    depth[1]=1;
    while(!q.empty()){
        u=q.front(); q.pop();
        for(int i=head1[u];i!=-1;i=edge1[i].next){
            v=edge1[i].v;
            if(depth[v]) continue;
            depth[v]=depth[u]+1;
            father[v]=u;
            q.push(v);
            fa[v][0]=u;
            for(int j=1;j<=t;++j)
                fa[v][j]=fa[fa[v][j-1]][j-1];
        }
    }
}

int LCA(int x,int y){
    if(depth[x]<depth[y]) swap(x,y);
    for(int i=t;i>=0;--i)
        if(depth[fa[x][i]]>=depth[y]) x=fa[x][i];
    if(x==y) return x;
    for(int i=t;i>=0;--i)
        if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}

int fre[maxn];

int myfind(int x){
    int r=x;
    while(r!=fre[r]) r=fre[r];
    int i=x,j;
    while(i!=r){
        j=fre[i];
        fre[i]=r;
        i=j;
    }
    return r;
}
int res;//桥;
void updata(int x,int y){
    int lca=LCA(x,y);
    int fx,fy;
    while(1){
        fx=myfind(x);
        fy=myfind(lca);
        if(fx==fy) break;
        fre[fx]=myfind(father[x]);
        --res;
        x=father[x];
    }
    while(1){
        fx=myfind(y);
        fy=myfind(lca);
        if(fx==fy) break;
        fre[fx]=myfind(father[y]);
        --res;
        y=father[y];
    }
}

int main(){
    int n,m,q,u,v,cc=0;
    while(scanf("%d%d",&n,&m)!=EOF&&(n||m)){
        init();
        init1();

        while(m--){
            scanf("%d%d",&u,&v);
            add(u,v,1);
            add(v,u,1);
        }
        num=0;
        cnt=0;
        memset(bridge,0,sizeof(bridge));
        memset(dfn,0,sizeof(dfn));
        memset(belong,0,sizeof(belong));
        //标记所有桥;
        for(int i=1;i<=n;++i)
            if(!dfn[i]) tarjan(i,-1);
        //边双连通分量划分;
        for(int i=1;i<=n;++i)
            if(!belong[i]){
                ++cnt;
                dfs(i);
            }
        for(int i=0;i<top;++i){
            u=edge[i].v;
            v=edge[i^1].v;
            if(belong[u]!=belong[v])//重边没有关系的;
                add1(belong[u],belong[v],1),add1(belong[v],belong[u],1);
        }
        for(int i=1;i<=cnt;++i) fre[i]=i;
        bfs();
        scanf("%d",&q);
        printf("Case %d:\n",++cc);
        res=cnt-1;
        while(q--){
            scanf("%d%d",&u,&v);
            if(belong[u]==belong[v]) printf("%d\n",res);
            else{
                updata(belong[u],belong[v]);
                printf("%d\n",res);
            }
        }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值