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;
}