HDU2586 How far away ?(LCA离线算法)

LCA(最近公共祖先)的离线算法,用到的是 tarjan 的思想,并用并查集标记父亲节点。

说说我的理解:

我们从根开始深搜遍历树,每当回溯到一个节点时,那就意味着我们已经完成了该节点子树的遍历,显然这个节点就是子树中点以及其本身的最近公共祖先,以此类推到整个树。这里非常巧妙的一点是,对于一个点,只有完成了其子树的遍历,我们才改变其 父节点 的值(赋初值为father[ i ] = i),这样,对于每次询问(就是给出两点标号,要求求出两点间最短距离,算一次询问。假设为 A 和 B),我们搜到 A (或B)时,如果 B (或A)已经被访问过,那么向上回溯,直到第一个 父节点 已经改变的点,就是包括点 A 和点 B 在内子树的根,这就是我们要找的 A 和 B 的最近公共祖先。

推荐自己画图手动实现一下,或者自己出数据用单步调试跟踪一下,了解一下递归过程。

ps:参照http://www.cnblogs.com/ylfdrib/archive/2010/11/03/1867901.html

代码:

#include<cstdio>
#include<cstring>

const int N = 40001;

struct Edge{
	int e,v;
	int next;
}edge[2*N];

int n,m,e_num,head[N];
int x[N],y[N],z[N],f[N],dist[N],vis[N];

void AddEdge(int a,int b,int c){
	edge[e_num].e=b; edge[e_num].v=c; edge[e_num].next=head[a]; head[a]=e_num++;
	edge[e_num].e=a; edge[e_num].v=c; edge[e_num].next=head[b]; head[b]=e_num++;
}

int find(int x){
	if(f[x]!=x)
		return f[x]=find(f[x]);
	return f[x];
}

void tarjan(int k){
	int i;
	vis[k]=1;
	f[k]=k;
	for(i=1;i<=m;i++){//m次询问,z[i]保存的是点 x[i] 和 y[i] 最近公共祖先
		if(x[i]==k && vis[y[i]]) z[i]=find(y[i]);
		if(y[i]==k && vis[x[i]]) z[i]=find(x[i]);
	}
	for(i=head[k];i!=-1;i=edge[i].next){
		if(!vis[edge[i].e]){
			dist[edge[i].e]=dist[k]+edge[i].v;
			tarjan(edge[i].e);
			f[edge[i].e]=k;
		}
	}
}

int main()
{
	int t,a,b,c,i;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		e_num=0;
		memset(head,-1,sizeof(head));
		for(i=1;i<n;i++){
			scanf("%d%d%d",&a,&b,&c);
			AddEdge(a,b,c);
		}

		for(i=1;i<=n;i++){
			x[i]=y[i]=z[i]=0;
		}
		for(i=1;i<=m;i++){
			scanf("%d%d",&a,&b);
			x[i]=a; y[i]=b;
		}

		memset(vis,0,sizeof(vis));
		dist[1]=0;
		tarjan(1);

		for(i=1;i<=m;i++)
			printf("%d\n",dist[x[i]]+dist[y[i]]-2*dist[z[i]]);
	}
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值