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;
}
举杯独醉,饮罢飞雪,茫然又一年岁。 ------AbandonZHANG
原文地址:https://www.cnblogs.com/AbandonZHANG/p/4114033.html