POJ 3694 Network ★(边双连通分量+并查集缩点+LCA)

[题意]一个无向图可以有重边,下面q个操作,每次在两个点间连接一条有向边,每次连接后整个无向图还剩下多少桥(每次回答是在上一次连边的基础之上) [分析]好题,做完后涨了很多姿势~ 普通做法当然就是每加一条边重新算一次桥,但这样复杂度将达到O(q*M),显然要超时。。所以我们需要“动态地”在原图的基础上求桥~ 我们可以先把图求一次边双连通分量(BCC)然后缩点,因为同一双连通分量中没有桥,加边没有影响。一个很重要的性质就是 一个图求一次边双连通分量缩点后将变成一颗树或者森林,并且树中的每条边都是桥}。因为此题中说所有的点都有边连着,所以这里缩点后是一棵树。 显然,树中每添加一条边,就会形成一个环,而这个环中的边将不再是桥,并且他们构成新的边双连通分量,所以我们每次在桥中减去这些边,并把他们缩成一个点。 第一, 怎么求在树中加边(u,v)后形成的环? 我们可以求出u,v的LCA,然后环就是v->LCA(u,v)->u>(u,v)->v. 第二, 怎么缩点? 以前一直做的是“静态缩点”,就是求一遍边BCC后图的结构就不变了,此时我们可以在求出每个点所属的BCC(bcc[i])后以BCC的标号来代替缩后的点。如果我们动态地加边,则每次都要修改原BCC中所有的点成新BCC,所以直接这样改行不通。这里我们是不是发现它很像……并查集?对!就是用并查集维护bcc[]!这是我做这道题学到的最重要的姿势 用并查集维护动态加边的缩点。 当然此题中我们没有用到bcc[],因为在求LCA时我们用的朴素的方法:{用level[]表示每个节点的深度,两个点同时向根爬,深度深的节点先爬(LCA(u,v) = LCA(u, father[v]) ),深度相同时两个点一起爬(LCA(u, v) = LCA(father[u], father[v]) ),直到两个点相同。}这种方法的好处是可以一边求LCA一边缩点。那么我们就需要一个father[]来维护节点的父节点。这样我们就不需要bcc[]了,直接用并查集维护father[],并用它表示缩点及所属BCC。  

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define MID(x,y) ((x+y)/2)
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;

const int MAXV = 100005;
const int MAXE = 400005;
struct node{
    int u, v;
    int next;
    int opp;                    //把一个无向边拆成两个有向边,对应反向边标号
}arc[MAXE];
int cnt, head[MAXV];
void init(){
    cnt = 0;
    mem(head, -1);
    return ;
}
void add(int u, int v){
    arc[cnt].u = u;
    arc[cnt].v = v;
    arc[cnt].next = head[u];
    arc[cnt].opp = cnt + 1;
    head[u] = cnt ++;
    arc[cnt].u = v;
    arc[cnt].v = u;
    arc[cnt].next = head[v];
    arc[cnt].opp = cnt - 1;
    head[v] = cnt ++;
    return ;
}
int id, dfn[MAXV], low[MAXV];
int level[MAXV];
int father[MAXV];               //点的父节点,便于爬山坡(向根节点走),也用于并查集缩点
int bridge_num, bridge[MAXV];   //标记该边是不是桥,这里用桥的子节点表示
bool vis_arc[MAXE];             //一条边无向边(两个有向边)只访问一次,
int find(int u){                //并查集 + 路径压缩处理缩点, 缩点合并边时把子节点的父亲设为父节点
    if(father[u] != u)
        return father[u] = find(father[u]);
    else
        return u;
}
void tarjan(int u, int l){
    dfn[u] = low[u] = ++id;
    level[u] = l;
    for (int i = head[u]; i != -1; i = arc[i].next){
        int v = arc[i].v;
        if (vis_arc[i]) continue;
        vis_arc[i] = vis_arc[arc[i].opp] = 1;
        if (!dfn[v]){
            father[v] = u;
            tarjan(v, l+1);
            low[u] = min(low[u], low[v]);
        }
        else{
            low[u] = min(low[u], dfn[v]);
        }
        if (dfn[u] < low[v]){
            bridge[v] = 1;
            bridge_num ++;
        }
        else{
            int x = find(u);
            int y = find(v);
            if (x != y)
                father[y] = x;
        }
    }
}
void solve(int n){
    id  = bridge_num = 0;
    mem(dfn, 0);
    mem(low, 0);
    mem(level, 0);
    mem(bridge, 0);
    mem(vis_arc, 0);
    for (int i = 0; i <= n; i ++)
        father[i] = i;
    for (int i = 1; i <= n; i ++){
        if (!dfn[i])
            tarjan(1, 1);
    }
    return ;
}
vector  path;
int lca(int u, int v){
    path.clear();
    if (level[u] > level[v])    swap(u, v);
    while(u != v){
        while(level[u] != level[v]){
            if(level[v] > level[u]){
                if (bridge[v]){
                    bridge[v] = 0;
                    bridge_num --;
                }
                path.push_back(v);
                v = father[v];
            }
            else{
                if (bridge[u]){
                    bridge[u] = 0;
                    bridge_num --;
                }
                path.push_back(u);
                u = father[u];
            }
        }
        while(u != v){
            if (bridge[v]){
                bridge[v] = 0;
                bridge_num --;
            }
            if (bridge[u]){
                bridge[u] = 0;
                bridge_num --;
            }
            path.push_back(u);
            path.push_back(v);
            v = father[v];
            u = father[u];
        }
    }
    int lc = u;
    //把加边后的环用并查集缩成一个点
    for (int i = 0; i < (int)path.size(); i ++){
        int u = path[i];
        father[u] = lc;
    }
    return bridge_num;
}
int main(){
    int n, m, t = 1;
    while(scanf("%d %d", &n, &m) != EOF){
        if (n + m == 0)
            break;
        init();
        for (int i = 0; i < m; i ++){
            int u, v;
            scanf("%d %d", &u, &v);
            add(u, v);
        }
        solve(n);
        int q;
        scanf("%d", &q);
        printf("Case %d:\n", t);
        for (int i = 0; i < q; i ++){
            int u, v;
            scanf("%d %d", &u, &v);
            printf("%d\n", lca(u, v));
        }
        puts("");
        t ++;
    }
	return 0;
}

转载于:https://www.cnblogs.com/AbandonZHANG/archive/2013/06/15/4114033.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值