AcWing 364. 网络

Solution

tarjan 边双缩点成一棵树,然后用并查集优化。
然后看到很多 dalao 都写了个 树上倍增求 lca , 其实不用。

用并查集处理出

int par[N];
int Find(int x) { return ((par[x]==x) ? x : par[x]=Find(par[x])); }
// Find(x) 找到下一个 x 的祖先(包括 x)没有被缩的点。

预处理出
每个点的 father 和 深度 depth.
然后我们可以参考树链剖分的代码,把其中的 top[x] 换成 Find(x) 来跑即可。
转化思路

  • 刚开始时 每个点都是一条树链。
  • 加入一条边 \((x,y)\) 其实是把 \(x\)\(lca(x,y)\) 的链合并成一条,\(y\) 同理。

正确性:可以参考树链剖分求 lca 的证明。
时间复杂度

  • tarjan 的复杂度 \(O(n+m)\)
  • Query 的复杂度 , 平摊来算,每条边只会被访问一次,所以至多 \(O(m)\)

Code

#include<set>
#include<map>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;
typedef long long LL;
typedef unsigned long long ULL;

const int N=8e5+5;
int one[N],hs[N],idx=1;
int ver[N],Next[N];
void AddEdge(int h[],int a,int b)
{
	Next[++idx]=h[a];
	h[a]=idx;
	ver[idx]=b;
}

// 边双联通分量的判断 dfn[x]==low[x] 
int dfn[N],low[N],times;
int id[N],all;
stack<int> S;

void tarjan(int x,int from)
{
	dfn[x]=low[x]=++times;
	S.push(x);
	int i,y;
	for(i=one[x];i;i=Next[i]) {
		y=ver[i];
		if(!dfn[y]) {
			tarjan(y,i);
			low[x]=min(low[x],low[y]);
		}
		else if(i!=(from^1)) 
			low[x]=min(low[x],dfn[y]);
	}
	
	if(low[x]==dfn[x]) {
		all++;
		while(S.top()!=x) 
			id[S.top()]=all,S.pop();
		id[x]=all,S.pop();
	}
}

int par[N];
int Find(int x) { return ((par[x]==x) ? x : par[x]=Find(par[x])); }
// Find(x) 找到下一个 x 的祖先(包括 x)没有被缩的点。
 
int ans;
int dep[N],fat[N];
void deal_first(int x,int fa)
{
	dep[x]=dep[fa]+1;
	fat[x]=fa;
	int i,y;
	for(i=hs[x];i;i=Next[i]) {
		y=ver[i];
		if(y==fa) continue;
		deal_first(y,x);
	}
}

void TQuery(int x,int y)
{
	while(Find(x)!=Find(y)) {
		if(dep[Find(x)]<dep[Find(y)]) 
			swap(x,y);
		ans--;
		x=Find(x),par[x]=fat[x],x=fat[x];
	}
}

int n,m,Q,T;

int main()
{
//	freopen("1.in","r",stdin);
	int i,x,y;
	
	while(scanf("%d%d",&n,&m),max(n,m)) {
		memset(dfn,0,sizeof dfn);
		memset(low,0,sizeof low);
		memset(id,0,sizeof id);
		memset(one,0,sizeof one);
		memset(hs,0,sizeof hs);
		memset(dep,0,sizeof dep);
		memset(fat,0,sizeof fat);
		while(S.size()) S.pop();
		times=all=0;
		idx=1;
		
		for(i=1;i<=m;i++) {
			scanf("%d%d",&x,&y);
			AddEdge(one,x,y),AddEdge(one,y,x);
		}
		
		for(i=1;i<=n;i++) 
			if(!dfn[i]) tarjan(i,0);
		
		for(x=1;x<=n;x++) 
			for(i=one[x];i;i=Next[i]) {
				y=ver[i];
				if(id[x]!=id[y]) AddEdge(hs,id[x],id[y]);
			} 
		ans=all-1;
		for(i=1;i<=all;i++) par[i]=i;
		deal_first(1,0);
		
		scanf("%d",&Q);
		printf("Case %d:\n",++T);
		while(Q--) {
			scanf("%d%d",&x,&y);
			TQuery(id[x],id[y]);
			printf("%d\n",ans);	
		}
		printf("\n");
	}
	return 0;
}

这里放上 AcWing 2568. 树链剖分 的部分代码用于对比。

LL TQuery(int x,int y)
{
	LL res=0;
	while(top[x]!=top[y]) {
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		res+=Prefix(dfn[x])-Prefix(dfn[top[x]]-1);
		x=top[x],x=fat[x];
	}
	if(dep[x]<dep[y]) swap(x,y);
	res+=Prefix(dfn[x])-Prefix(dfn[y]-1);
	return res;
}
posted @ 2020-11-28 21:04  cjlworld  阅读(64)  评论(0编辑  收藏  举报