题目链接:POJ-3694
**题意:**给出一张n个点m条边的无向连通图,q次操作,每次添加一条无向边,然后输出当前图的桥的数量。
数据范围:
n
<
=
1
0
5
,
m
<
=
2
∗
1
0
5
,
q
<
=
1
0
3
n<=10^5,m<=2*10^5,q<=10^3
n<=105,m<=2∗105,q<=103
首先我们先跑一次tarjan将边双连通分量求出来,将这些连通分量缩为一个点,建一张新图,建出来的图一定是一颗树,此时桥的数量就是连通分量的个数-1.
考虑在x与y之间添加一条边,我们在缩点之后的树中添加,用belong[]来表示缩点后点x对应的连通分量编号,那么
- 如果 b e l o n g [ x ] = = b e l o n g [ y ] belong[x]==belong[y] belong[x]==belong[y],此时是在连通分量内部添加边,故不会改变桥的数量。
- 如果不一致的话,需要先求出 z = L C A ( b e l o n g [ x ] , b e l o n g [ y ] ) z=LCA(belong[x],belong[y]) z=LCA(belong[x],belong[y]),考虑将添边操作转化为在 b e l o n g [ x ] 与 z , b e l o n g [ y ] 与 z belong[x]与z,belong[y]与z belong[x]与z,belong[y]与z之间添边,那么这中间有多少条边,桥就会减少多少(因为他们构成了环)。那么可以从belong[x]开始不断的向上一步步的走向lca,将这中间没有被标记过的边标记出来,并且桥的数量减1,另一边也是同理,时间复杂度为 O ( n ∗ q ) O(n*q) O(n∗q)
- 当然对于操作二还可以用并查集来优化,就是不断的将x合并到他的父亲中去,那么已经被标记过的边就不会再走,而是直接跳到没有标记过的边的父亲节点去:具体看下面代码。
//fre为并查集,father为x的直接父亲;
void updata(int x,int y){
int lca=LCA(x,y);
int fx,fy;
while(1){
fx=myfind(x);//并查集;
fy=myfind(lca);
if(fx==fy) break;
fre[fx]=myfind(father[x]);
--res;
x=father[x];
}
while(1){
fx=myfind(y);
fy=myfind(lca);
if(fx==fy) break;
fre[fx]=myfind(father[y]);
--res;
y=father[y];
}
}
好,总结一下:
- 先求割边,然后边双连通分量划分。
- 建新图(树),预处理lca一系列东西。
- 进行q次操作。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
struct Edge{
int v,w,next;
}edge[500009],edge1[500009];
int head[maxn],top;
int head1[maxn],top1;
void add(int u,int v,int w){
edge[top].v=v;
edge[top].w=w;
edge[top].next=head[u];
head[u]=top++;
}
void add1(int u,int v,int w){
edge1[top1].v=v;
edge1[top1].w=w;
edge1[top1].next=head1[u];
head1[u]=top1++;
}
void init(){
top=0;
memset(head,-1,sizeof(head));
}
void init1(){
top1=0;
memset(head1,-1,sizeof(head1));
}
bool bridge[500009];
int belong[maxn];
int cnt;
int dfn[maxn],low[maxn],num;
//求割边;
void tarjan(int u,int last){
dfn[u]=low[u]=++num;
int v;
for(int i=head[u];i!=-1;i=edge[i].next){
v=edge[i].v;
if(!dfn[v]){
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])
bridge[i]=bridge[i^1]=1;
}
else if(last==-1||(last^1)!=i)//因为我的前向星是从0开始存储的;
low[u]=min(low[u],dfn[v]);
}
}
//连通分量划分;
void dfs(int u){
belong[u]=cnt;
for(int i=head[u];i!=-1;i=edge[i].next){
if(belong[edge[i].v]||bridge[i]) continue;
dfs(edge[i].v);
}
}
int depth[maxn];
int fa[maxn][19];
int father[maxn];
int t=18;
//lca预处理;
queue<int> q;
void bfs(){
memset(depth,0,sizeof(depth));
memset(fa,0,sizeof(fa));
int u,v;
while(!q.empty()) q.pop();
q.push(1);
depth[1]=1;
while(!q.empty()){
u=q.front(); q.pop();
for(int i=head1[u];i!=-1;i=edge1[i].next){
v=edge1[i].v;
if(depth[v]) continue;
depth[v]=depth[u]+1;
father[v]=u;
q.push(v);
fa[v][0]=u;
for(int j=1;j<=t;++j)
fa[v][j]=fa[fa[v][j-1]][j-1];
}
}
}
int LCA(int x,int y){
if(depth[x]<depth[y]) swap(x,y);
for(int i=t;i>=0;--i)
if(depth[fa[x][i]]>=depth[y]) x=fa[x][i];
if(x==y) return x;
for(int i=t;i>=0;--i)
if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int fre[maxn];
int myfind(int x){
int r=x;
while(r!=fre[r]) r=fre[r];
int i=x,j;
while(i!=r){
j=fre[i];
fre[i]=r;
i=j;
}
return r;
}
int res;//桥;
void updata(int x,int y){
int lca=LCA(x,y);
int fx,fy;
while(1){
fx=myfind(x);
fy=myfind(lca);
if(fx==fy) break;
fre[fx]=myfind(father[x]);
--res;
x=father[x];
}
while(1){
fx=myfind(y);
fy=myfind(lca);
if(fx==fy) break;
fre[fx]=myfind(father[y]);
--res;
y=father[y];
}
}
int main(){
int n,m,q,u,v,cc=0;
while(scanf("%d%d",&n,&m)!=EOF&&(n||m)){
init();
init1();
while(m--){
scanf("%d%d",&u,&v);
add(u,v,1);
add(v,u,1);
}
num=0;
cnt=0;
memset(bridge,0,sizeof(bridge));
memset(dfn,0,sizeof(dfn));
memset(belong,0,sizeof(belong));
//标记所有桥;
for(int i=1;i<=n;++i)
if(!dfn[i]) tarjan(i,-1);
//边双连通分量划分;
for(int i=1;i<=n;++i)
if(!belong[i]){
++cnt;
dfs(i);
}
for(int i=0;i<top;++i){
u=edge[i].v;
v=edge[i^1].v;
if(belong[u]!=belong[v])//重边没有关系的;
add1(belong[u],belong[v],1),add1(belong[v],belong[u],1);
}
for(int i=1;i<=cnt;++i) fre[i]=i;
bfs();
scanf("%d",&q);
printf("Case %d:\n",++cc);
res=cnt-1;
while(q--){
scanf("%d%d",&u,&v);
if(belong[u]==belong[v]) printf("%d\n",res);
else{
updata(belong[u],belong[v]);
printf("%d\n",res);
}
}
}
return 0;
}
本文探讨了在给定的无向连通图上动态更新桥的数量的算法。通过Tarjan算法求出边双连通分量,利用LCA预处理和并查集优化,实现对图中桥数量的高效更新。
244

被折叠的 条评论
为什么被折叠?



