HDU4547(tarjan算法 离线LCA)

这篇博客介绍了作者初次接触并掌握Tarjan算法解决离线最近公共祖先(LCA)问题的过程。通过使用并查集数据结构,博主详细解释了如何进行后序DFS,并在所有询问输入完毕后一次性处理。文章提到,虽然算法理解起来相对简单,但初次接触可能会感到不习惯,多练习可以提高熟练度。文中还给出了AC代码供读者参考。
摘要由CSDN通过智能技术生成

我也是第一次接触tarjan算法,来来回回花了不少时间才把这道水题做出来了。

离线算法(就是等询问全部输入完了再一并处理),算法本身其实挺容易理解。

就是需要用到的数据结构有点繁杂,第一次接触肯定不太习惯,多练几次就好了。

需要用到并查集来来储存集合,tarjan函数相当于一个后序的dfs,每次将一个节点的

的一个子树访问完之后,把把他融合到自己的这个集合【就是并查集】中,当全部子树访问完成之后,

把自己融合到自己父节点的集合中,假设p节点的全部子树访问完了,那么把p节点的

相关询问全部处理一遍,分两种情况,假设与p点相关的为q,1,q已经访问过,那么

p与q的lca等于 find(q)【并查集的查找操作】,2,q未被访问,那么说明q的顺序在p后面,

所以等到q作为处理点的时候再处理,所以存访问应该是无向的,即有 p->q 也把 q ->p 存下来。

下面是ac代码。

#include<iostream>
#include<vector>
#include<map>
#include<string>
#include<cstring>
#include<cstdio>
using namespace std;

vector<int> vec[100005];//图 
int pre[100005];//并查集
bool root[100005];//是否为根节点
bool visit[100005];//是否访问过
//上面这个是tarjan算法必须的数据结构
//然后根据具体的问题会添加其他的数据结构 
//比如下面 anslink[] ansarr[] dis[] LCA[] 

typedef struct node {
	int point;//到达的点 
	int number;//输入的顺序 
}node;
vector<node> anslink[100005];//结果处理顺序 
pair<int,int> ansarr[100005];//结果输入顺序 
int dis[100005];//距离根节点的位置 
int LCA[100005];//根据询问顺序储存每个询问的LCA 

int find(int u) {
	if(pre[u]==u) return u;
	else return pre[u]=find(pre[u]);
}

void _union(int a,int b) {
	int x=find(a);
	int y=find(b);
	pre[y]=x;
}

void init() {
	for(int i=0;i<=100000;i++) {
		vec[i].clear();
		pre[i]=i;
		root[i]=true;
		visit[i]=false;
		anslink[i].clear();
		dis[i]=0;
	}
}

void tarjan(int s) {
	for(int i=0;i<vec[s].size();i++) {
	    dis[vec[s][i]]=dis[s]+1;
		tarjan(vec[s][i]);
		_union(s,vec[s][i]);
	}
	visit[s]=true;
	for(int i=0;i<anslink[s].size();i++) {
		node &t=anslink[s][i];
		if(visit[t.point]) {
			LCA[t.number]=find(t.point);
		}
	}
}

int main() {
    int T;
    scanf("%d",&T);
    for(int Case=1;Case<=T;Case++) {
	 	init();
	 	map<string,int> mp;//这个因题目而异,string对应编号
	 	int N,M,to=1;
		string s1,s2;
	 	scanf("%d%d",&N,&M);
	 	for(int i=1;i<N;i++) {	
	 		cin>>s1>>s2;
	 		int a,b;
	 		if(mp.find(s1)==mp.end()) {
	 			mp[s1]=to++;
	 			a=to-1;
			}
			else a=mp[s1];
			if(mp.find(s2)==mp.end()) {
				mp[s2]=to++;
				b=to-1;
			}
			else b=mp[s2];
	 		vec[b].push_back(a);
	 		root[a]=false;
		}
		to=1;
		for(int i=1;i<=M;i++) {
			string s1,s2;
			cin>>s1>>s2;
			int a=mp[s1],b=mp[s2];
			anslink[a].push_back(node{b,to});
			anslink[b].push_back(node{a,to++});
			ansarr[i].first=a;
			ansarr[i].second=b;
		}
		
		for(int i=1;i<=N;i++) {
			if(root[i]) {
		    	tarjan(i);
		    	break; 
			}
		} 
		for(int i=1;i<=M;i++) {
			int f=ansarr[i].first,s=ansarr[i].second;
			int len=dis[s]-dis[LCA[i]]==0?0:1;
			printf("%d\n",dis[f]-dis[LCA[i]]+len);
		}
    } 
 return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值