POJ - 3694 Network(边连通分量缩点+dfn上朴素LCA+并查集动态缩点)

http://poj.org/problem?id=3694


首先注意这个题中有重边。有重边的话除了vector<>外开一个map记录一下这个边有没有加过,如果加过了就不要再加了,会影响判断。注意poj中map的pair用列表初始化{}会CE,需要make_pair.

普通做法当然就是每加一条边重新算一次桥,但这样复杂度将达到O(q*M).

然后考虑缩点,缩完点后变成了一颗树。如果不懂边连通缩点先去看下上一篇博客(讲边缩点的)

我们需要“动态地”在原图的基础上求桥。

可以发现变成一颗树后,树上的每一条边都是一个边连通分量,当新连的边是一个连通分量内部的点的时候,桥的数量不变。当连的是两个连通分量上的点的时候,会发现有一个环出现,那么环上的原有的所有边将不再是桥。

怎么求出这个环呢?

我们可以求出u,v的LCA,然后环就是v->LCA(u,v)->u>(u,v)->v.

怎么缩点? 以前一直做的是“静态缩点”,就是求一遍边后图的结构就不变了。如果我们动态地加边,则每次都要修改图中所有的点成新缩点,所以直接这样改行不通。这里是不是发现它很像并查集?只要一次遍历到lca上之后把一个环全部通过并查集缩成一个新点就好。每次查询也就是查点的并查集祖先。

这就是这道题的新姿势:用并查集维护动态加边的缩点。 

原来在求LCA的朴素的方法是{用level[]表示每个节点的深度,两个点同时向根爬,深度深的节点先爬(LCA(u,v) = LCA(u, father[v]) ),深度相同时两个点一起爬(LCA(u, v) = LCA(father[u], father[v]) ),直到两个点相同。}这种方法的好处是可以一边求LCA一边缩点。那么我们就需要一个father[]来维护节点的父节点。这样就可以直接用并查集维护father[],并用它表示缩点及所属BCC。

在这里有一个在tarjan上求lca的一样的办法,但是更为方便,就是用dfn来求lca。通过模拟发现,原来通过深度朴素求lca的代码如下。深度一个一个加。用dfn求lca也是可以一样的办法,如果两边的dfn相差巨大,那么在搜索树中大的dfn[x]最后会到一个小的地方,最小会到根节点,不然就到他们近一点的父亲祖先节点。总之模拟发现方法是等价的。

模拟的话可以直接用一颗本来就都是连通分量的树图来模拟。

这样子的话我们在维护lca的时候顺带用并查集不断更新桥的数量和把新的环缩成一个点就好了。

int lca(int a,int b){
    while(d[a] > d[b]){
        a = f[a];
    }
    while(a != b){
        a = f[a];
        b = f[b];
    }
    if(a == b){
        return a;
    }
}
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<stack>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+100;
typedef int LL;
vector<LL>g[maxn];
map<pair<LL,LL>,LL>ma;
LL n,m;
LL low[maxn],dfn[maxn],times=0,bridges=0,fa[maxn],pre[maxn];//fa为并查集,pre表示某点的父亲节点 
bool vis[maxn];//标记桥 
void init()
{
	for(LL i=0;i<=n;i++) fa[i]=i,g[i].clear();
	memset(pre,0,sizeof(pre));
	memset(vis,0,sizeof(vis));
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	times=0;bridges=0;
	ma.clear();
}
LL find(LL x){ if(fa[x]==x) return fa[x]; return fa[x]=find(fa[x]);}
void unionn(LL a,LL b)
{
	LL x=find(a);LL y=find(b);
	if(x!=y) fa[x]=y;
}
void tarjan2(LL u)
{
    dfn[u]=low[u]=++times;
    for(LL i=0;i<g[u].size();i++){
        LL to=g[u][i];
        if(!dfn[to]){
        	pre[to]=u;
            tarjan2(to);
            if(low[to]<=dfn[u]){
            	unionn(u,to);
			}
            if(low[to]>dfn[u])
            {
                 vis[to]=vis[u]=true;
                 bridges++;
            }
            low[u]=min(low[u],low[to]);
        }
        else if(to!=pre[u])low[u]=min(low[u],dfn[to]);
    }

}
LL lca(LL x,LL y)
{
	if(dfn[x]<dfn[y]) swap(x,y);
	while(dfn[x]>dfn[y])
	{
		if(find(x)!=find(pre[x])){
			fa[find(x)]=find(pre[x]);bridges--;
		}
		x=pre[x];
	}
	while(x!=y)
	{
		if(find(y)!=find(pre[y])){
			fa[find(y)]=find(pre[y]);bridges--;
		}
		y=pre[y];	
	}
	
	return bridges;
}
int main(void)
{
  
  int cas=1;
  while(scanf("%d%d",&n,&m)&&(n+m))
  {
  	init();
  
  	for(LL i=1;i<=m;i++)
  	{
     	LL u,v;scanf("%d%d",&u,&v);
		if(!ma.count(make_pair(u,v))) g[u].push_back(v);g[v].push_back(u);
		ma[make_pair(u,v)]++;ma[make_pair(u,v)]++;
  	}
  	for(LL i=1;i<=n;i++){
  		
    	if(!dfn[i]) tarjan2(i);
  	}
  	LL Q;scanf("%d",&Q);
  	printf("Case %d:\n",cas++);    	
  	for(LL k=1;k<=Q;k++){
  		LL x,y;cin>>x>>y;
		cout<<lca(x,y)<<endl;	
	}
	printf("\n");
  }
  return 0;
}

猜你喜欢

转载自blog.csdn.net/zstuyyyyccccbbbb/article/details/108906344
今日推荐