离线做法
针对有询问的题目,在线做法为读入一个询问立刻给出输出;离线做法为读入所有询问后再给出输出
Tarjan—离线求LCA
对向上标记法的优化
在深度优先遍历的同时,将所有点分为三类
01:已经被搜索且回溯过的点
02:当前搜索分支上的点
03:未搜索到的点
若有在02集合中的点被查询,则检查另一个点是否为01集合中的点,采用并查集来处理01集合中的点,用其根节点代替,根节点即为二者的最近公共祖先
如何求树上两点间的最小距离
结点x,结点y的最小距离=d[x]+d[y]-2*d[lca(x,y]
d[]表示到根结点的距离
例题:距离
给出 n个点的一棵树,多次询问两点之间的最短距离。
注意:
- 边是无向的。
- 所有节点的编号是 1,2,…,n
输入格式
第一行为两个整数 n和 m。n 表示点数,m表示询问次数;接下来 n−1行,每行三个整数 x,y,k,表示点 x 和点 y 之间存在一条边长度为 k
再接下来 m行,每行两个整数 x,y,表示询问点 x 到点 y的最短距离。
树中结点编号从 1到 n
输出格式
共 m行,对于每次询问,输出一行询问结果。
数据范围
2≤n≤104
1≤m≤2×104,
0<k≤100,
1≤x,y≤n
输入样例1:
2 2
1 2 100
1 2
2 1
输出样例1:
100
100
输入样例2:
3 2
1 2 10
3 1 15
1 2
3 2
输出样例2:
10
25
AC_code
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
const int N=2e4+10;
int n,m;
int h[N],w[N*2],e[N*2],ne[N*2],idx;
vector<PII> query[N];//first:要查询的另一个点,second:查询编号
int res[N];
int p[N];
int dist[N];
int st[N];
//st=0:当前结点未被遍历过,未被回溯过
//st=1:当前结点被遍历过,但被回溯过
//st=2:当前结点被遍历过,且被回溯过
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int find(int x)//并查集
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
void dfs(int u,int fa)//求结点u到根节点的距离
{
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==fa) continue;
dist[j]=dist[u]+w[i];
dfs(j,u);
}
}
void tarjan(int u)
{
st[u]=1;//表示当前点被遍历到
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(!st[j])
{
tarjan(j);
p[j]=u;//结点j的父节点为u
}
}
for(auto item:query[u])
{
int y=item.first,id=item.second;
if(st[y]==2)
{
int anc=find(y);
res[id]=dist[u]+dist[y]-2*dist[anc];
}
}
st[u]=2;
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=1;i<n;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
if(a!=b)
{
query[a].push_back({b,i});
query[b].push_back({a,i});
}
}
for (int i=1;i<=n;i++) p[i]=i;//并查集初始化
dfs(1,-1);
tarjan(1);//任意一个点
for(int i=1;i<=m;i++) cout<<res[i]<<endl;
return 0;
}
博客介绍了离线做法,即读入所有询问后再输出。重点讲解Tarjan离线求LCA,对向上标记法优化,在深度优先遍历中把点分类,用并查集处理。还说明了求树上两点最小距离的公式,最后给出相关例题及输入输出格式。
241

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



