我也是第一次接触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;
}

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

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



