500道题刚做了140道就开始做不动了。。。估计要凉。。。
还是先把LCA看完。。。(以下内容 by 路人黑的纸巾,写的十分详细)
LCA(Lowest Common Ancestors),即最近公共祖先
在一棵树上,两个节点的深度最浅的公共祖先就是LCA (自己可以是自己的祖先)
其实LCA的用途很广,我个人认为OI里用LCA最多的像是这样子的题:
给你一棵树(或者是一个图求完MST的树,这种会比较常见)和树上的两点,找出这两点之间的路径的最大值或最小值或其他东东
这时我们可以借助这两点的LCA作为中转点来轻松地解决这类题目
方法1:tarjan算法(离线)求LCA
方法2:ST(RMQ)算法(在线)求LCA
注意事项
dfs序的长度是2n−1,用dfsO(n)处理出r、deep和dfs序之后直接套上裸RMQ (线段树?差不多的……别在意这个)
设f[i][j]表示dfs序中j~j+2^i-1的点当中,deep值最小的是哪个点 即可
每次询问时间复杂度O(1),完美
附大佬代码:
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int n,_n,m,s;//_n是用来放元素进dfs序里,最终_n=2n-1
struct EDGE
{
int to;
EDGE* las;
}e[1000001];//前向星存边
EDGE* last[500001];
int sx[1000001];//顺序,为dfs序
int f[21][1000001];//用于ST算法
int deep[500001];//深度
int r[500001];//第一次出现的位置
int min(int x,int y)
{
return deep[x]<deep[y]?x:y;
}
void dfs(int t,int fa,int de)
{
sx[++_n]=t;
r[t]=_n;
deep[t]=de;
EDGE*ei;
for (ei=last[t];ei;ei=ei->las)
if (ei->to!=fa)
{
dfs(ei->to,t,de+1);
sx[++_n]=t;
}
}
int query(int l,int r)
{
if (l>r)
{
//交换
l^=r;
r^=l;
l^=r;
}
int k=int(log2(r-l+1));
return min(f[k][l],f[k][r-(1<<k)+1]);
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
int j=0,x,y;
for (int i=1;i<n;++i)
{
scanf("%d%d",&x,&y);
e[++j]={y,last[x]};
last[x]=e+j;
e[++j]={x,last[y]};
last[y]=e+j;
}
dfs(s,0,0);
//以下是ST算法
for (int i=1;i<=_n;++i)f[0][i]=sx[i];
int ni=int(log2(_n)),nj,tmp;
for (int i=1;i<=ni;++i)
{
nj=_n+1-(1<<i);
tmp=1<<i-1;
for (j=1;j<=nj;++j)
f[i][j]=min(f[i-1][j],f[i-1][j+tmp]);
}
//以下是询问,对于每次询问,可以O(1)回答
while (m--)
{
scanf("%d%d",&x,&y);
printf("%d\n",query(r[x],r[y]));
}
}方法3:树上倍增找LCA
在hihocoder上的最近公共祖先 一 暴力都WA 已经绝望
昨晚的小白月赛。。。还行吧,按理说应该AK,只是卡特兰数卡了一点时间,最后俩题没来得及看,难度中等偏下。
目测做够500道基本不可能了。。。
但是还是要努力做啊。。。

本文深入讲解了LCA(最近公共祖先)算法的三种实现方法,包括tarjan算法、ST(RMQ)算法以及树上倍增算法,并提供了详细的代码示例。
1148

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



