POJ3694 Network (板子题)

题目链接: POJ3694 Network
题目大意:
一张 \(N\) 个点,\(M\) 条边的无向连通图,\(Q\)次操作,每次添加一条边,同时询问图中割边的数量。
\(1\leq N\leq 100,000\)\(1\leq M\leq 200,000\)\(1\leq Q\leq 1000\)
思路
用来熟悉 \(e\_DCC\) 缩点的板子题。
首先图中的边双连通分量自身不受加边的影响,割边数量为零,将它们缩点,然后就得到了一颗树,这个过程中先计算出 \(ans\) 的初始值。
树上两点 \(u,v\) 相连时会产生环,相当于 \(u\)\(v\) 的路径上的所有割边都不再是割边,具体来说就是两个节点分别向 \(lca\) 跳,并将途中的割边标记为普通边。
考虑到要多次对树进行操作,可以使用并查集记录一个点向上的第一条割边的位置,当其变成普通边后,将子节点合并到父节点中,同时 \(ans--\)
动态查询 \(O(M+NlogN)\) ,离线 \(O(M+N)\) 。( \(LCA\) 实现不同)

可能会有人对于直接在树上标记这个操作有疑问,因为实际上树加了边后形态就变了,但其实仔细想一想会发现这是合理的,跳并查集的过程其实就是略过所有非割边的边,相当于从一个新的 \(e\_DCC\) 中直接跳出来,到剩下的割边上,而合并并查集的过程就是合并两个 \(e\_DCC\) 的过程,算法正确性没有问题。

细节:

  • 求割边的时候记录边,不记录父亲
  • 因为是多组数据,有一堆鬼畜的初始化,要注意一些
  • 1Y的,在POJ Best Solutions上面竟然进了前两页,非常感动

Code:

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100100
#define M 200100
#define LOG 17
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
inline int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
    return s*w;
}
int head[N],to[M*2],nxt[M*2]; //原图
int H[N],T[M*2],NX[M*2]; //缩点后的图
int dfn[N],low[N],c[N]; //c[i]:i所在的e_DCC编号
int f[N][LOG],dep[N];
int fa[N];
int cnt,num,dcc,t_e; //cnt:原图边计数 num:时间戳计数 dcc:e_DCC计数 t_e:新图计数
int n,m,ans;
bool bridge[M*2];
void init(int n){
    mem(head,-1),mem(H,-1);
    mem(dfn,0),mem(c,0);
    mem(bridge,false);
    for(int i=1;i<=n;i++)fa[i]=i;
    cnt=t_e=-1,ans=dcc=num=0;
}
void add_e(int a,int b,bool id){
    nxt[++cnt]=head[a];
    head[a]=cnt;
    to[cnt]=b;
    if(id)add_e(b,a,0);
}
void add_c(int a,int b){
    NX[++t_e]=H[a],H[a]=t_e,T[t_e]=b;
}
void tarjan(int x,int e){
    dfn[x]=low[x]=++num;
    for(int i=head[x];~i;i=nxt[i]){
        if(i==(e^1))continue;
        int y=to[i];
        if(dfn[y])low[x]=min(low[x],dfn[y]);
        else{
            tarjan(y,i);
            low[x]=min(low[x],low[y]);
            if(low[y]>dfn[x]){
                bridge[i]=bridge[i^1]=true,ans++;
            }
        }
    }
}
void dfs1(int x){
    c[x]=dcc;
    for(int i=head[x];~i;i=nxt[i]){
        if(c[to[i]]||bridge[i])continue;
        dfs1(to[i]);
    }
}
void dfs2(int x,int fath){
    f[x][0]=fath,dep[x]=dep[fath]+1;
    for(int i=1;i<LOG;i++){
        f[x][i]=f[f[x][i-1]][i-1];
    }
    for(int i=H[x];~i;i=NX[i]){
        if(T[i]==fath)continue;
        dfs2(T[i],x);
    }
}
int Lca(int a,int b){
    if(dep[a]<dep[b])swap(a,b);
    for(int i=LOG-1;i>=0;i--)
        if(f[a][i]&&dep[f[a][i]]>=dep[b])a=f[a][i];
    if(a==b)return a;
    for(int i=LOG-1;i>=0;i--)
        if(f[a][i]!=f[b][i])a=f[a][i],b=f[b][i];
    return f[a][0];
}
int find(int x){
    if(fa[x]==x)return x;
    return fa[x]=find(fa[x]);
}
int main(){
    int a,b,q,lca,T=1;
    while((n=read())|(m=read())){
        init(n);
        for(int i=0;i<m;i++)
            add_e(read(),read(),1);
        tarjan(1,-1);
        for(int i=1;i<=n;i++)
            if(!c[i])++dcc,dfs1(i);
        for(int i=0;i<=cnt;i++){
            int u=to[i],v=to[i^1];
            if(c[u]==c[v])continue;
            add_c(c[u],c[v]);
        }
        dfs2(1,0);
        printf("Case %d:\n",T++);
        q=read();
        while(q--){
            a=c[read()],b=c[read()];
            lca=Lca(a,b);
            a=find(a),b=find(b);
            while(dep[a]>dep[lca])
                fa[a]=find(f[a][0]), a=find(a),ans--;
            while(dep[b]>dep[lca])
                fa[b]=find(f[b][0]), b=find(b),ans--;
            printf("%d\n",ans);
        }
        puts("\n");
    }
    return 0;
}
posted @ 2021-01-26 13:05  Neal_lee  阅读(154)  评论(0编辑  收藏  举报