【割边缩点】解题报告:POJ - 3694 - Network(Tarjan割边缩点 + LCA + 并查集优化)

POJ - 3694 - Network

给定一张N个点M条边的无向连通图,然后执行Q次操作,每次向图中添加一条边,并且询问当前无向图中“桥”的数量。N105,M2105,Q1000N≤10^5,M≤2*10^5,Q≤1000

首先运行一次tarjan,求出桥和缩点,那么无向图缩点为一棵树,树边正好是原来的桥。每次操作连接两点,看看这两点是不是在同一个缩点内,如果是,那么缩点后的树没任何变化,如果两点属于不同的缩点,那么连接起来,然后找这两个缩点的LCA,,因为从点u到LCA再到点v再到点u,将形成环,里面的树边都会变成不是桥。计数的时候注意,有些树边可能之前已经被标记了,这次再经过不能再标记
利用并查集优化,当树上的边不再是桥了以后,就把这条边的子结点所在的集合合并到父节点所在的集合上,这样从c[x]c[x]走到LCA(c[x],c[y])LCA(c[x],c[y])时,每一步可以直接走到并查集Get返回的结点上,利用并查集对不再是桥的边进行路径压缩,这样将时间复杂度从原来的O(M+QN)O(M+Q*N)优化到O(M+QlogN)O(M+QlogN)
(第一次走的时候没有任何影响,但是再往后的数据,再次走到了以后会直接把前面走过的不用走的路全部跳过去,达到优化的目的)
 
AC代码:

//这道题代码虽然比较长,但是每个函数各司其职,井井有条,写起来非常舒服
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<math.h>
#include<cstring>
#include<bitset>
#include<vector>
#include<queue>
#define ls (p<<1)
#define rs (p<<1|1)
#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
#define lowbit(p) p&(-p)
using namespace std;

typedef long long ll;
typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 2e5+7;
const int M = 5e5+7;

int head[N],nex[M],ver[M],tot = 1;//原图
int hc[N],nc[M],vc[M],tc = 1;//缩点后建图
int dfn[N],low[N],num;//tarjan专用
int dcc,cnt;//强连通专用
int n,m,t,T;//输入专用
bool bridge[M];//割边专用
int deep[N],f[N][20];//LCA专用
int c[N];//缩点专用
int fa[N];//并查集专用

void add(int x,int y){
    ver[++tot] = y;nex[tot] = head[x];head[x] = tot;
}

void add_c(int x,int y){
    vc[++tc] = y;nc[tc] = hc[x];hc[x] = tc;
}

void tarjan(int x,int in_edge){
    dfn[x] = low[x] = ++num;
    for(int i = head[x];i;i = nex[i]){
        int y = ver[i];
        if(!dfn[y]){
            tarjan(y,i);
            low[x] = min(low[x],low[y]);
            if(low[y] > dfn[x])
                bridge[i] = bridge[i ^ 1] = true;//成对变换
        }
        else if(i != (in_edge ^ 1))//i和in_edge都是前向星的指针编号
            low[x] = min(low[x],dfn[y]);
    }
}

void dfs(int x){
    c[x] = dcc;
    for(int i = head[x];i;i = nex[i]){
        int y = ver[i];
        if(c[y] || bridge[i])continue;
        dfs(y);
    }
}

queue<int>q;

void bfs(){//求的是缩点后的图
    deep[1] = 1;//根节点的深度是1
    q.push(1);
    while(q.size()){
        int x = q.front();
        q.pop();
        for(int i = hc[x];i;i = nc[i]){
            int y = vc[i];
            if(deep[y])continue;
            deep[y] = deep[x] + 1;
            f[y][0] = x;
            over(j,1,19)
                f[y][j] = f[f[y][j - 1]][j - 1];
            q.push(y);
        }
    }
}

int lca(int x,int y){
    if(deep[x] > deep[y])swap(x,y);
    lver(i,18,0)
        if(deep[f[y][i]] >= deep[x])
            y = f[y][i];
    if(x == y)return x;
    lver(i,18,0)
    if(f[x][i] != f[y][i])
        x = f[x][i],y = f[y][i];
    return f[x][0];
}

int Get(int x){
    if(x == fa[x])return x;
    return fa[x] = Get(fa[x]);
}

int main()
{
    while(scanf("%d%d",&n,&m) != EOF && n){

        tot = 1;dcc = num = 0;
        over(i,1,n)
            head[i]  = hc[i] = dfn[i] = low[i] = deep[i] = c[i] = 0;
        over(i,1,2 * m + 1)
            bridge[i] = 0;
        over(i,1,m){
            int x,y;
            scanf("%d%d",&x,&y);
            add(x,y);add(y,x);
        }
        over(i,1,n)
            if(!dfn[i])
                tarjan(i,0);
        //两个套路都一样
        over(i,1,n)
            if(!c[i])
                ++dcc,dfs(i);
        //缩完点该建图了
        tc = 1;
        over(i,2,tot){//建图就用到了成对变换
                int x = ver[i ^ 1],y = ver[i];
                if(c[x] == c[y])continue;
                add_c(c[x],c[y]);
                //因为之前是无向图转有向图是双边,这里已经是有向图了只需要建单边即可
        }
        //lca预处理
        bfs();
        //并查集初始化
        over(i,1,dcc)
            fa[i] = i;
        scanf("%d",&t);
        int ans = dcc - 1;//dcc是缩点后的点数,而ans是边数,所以最开始ans,树的边数等于点数 - 1
        printf("Case %d:\n", ++T);
        while(t--){
            int x,y;
            scanf("%d%d",&x,&y);
            x = c[x],y = c[y];
            int p = lca(x,y);
            x = Get(x);
            while(deep[x] > deep[p]){
                fa[x] = f[x][0];//先给fa赋值
                ans--;
                x = Get(x);//这样get的时候x也能往上走
            }
            y = Get(y);
            while(deep[y] > deep[p]){
                fa[y] = f[y][0];
                ans--;
                y = Get(y);
            }
            printf("%d\n",ans);
        }
        cout<<endl;
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章