第一种:树链剖分@TOC
一:知识储备
- 重节点:以i为根的节点中结点数最多的结点
- 轻结点:其他结点
- 重链:由重节点连成的链
二:实现必需品:
- dep存深度
- son存重节点是谁
- siz存以i为根的子树大小
- fa存父亲是谁
- top存“头”
~重节点的头是他所在链的第一个出现的点
~轻结点的头是他自己
三:代码:
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn=100;
//siz是它对应的结点的子树个数,算上他自己
//son存储这个点的重儿子
struct Edge{
int to;//下一个点
int val;//边权
};
//这里使用邻接表存图,和链式前向星一样
vector<Edge>g[maxn];
int fa[maxn],siz[maxn],son[maxn],dep[maxn];
int top[maxn];
//求出每个点的重儿子
void dfs1(int u){
siz[u]=1;
son[u]=0;
for(int i=0;i<g[u].size();i++){//循环边
Edge &v=g[u][i];
if(fa[u]==v.to)continue;
dep[v.to]=dep[u]+1;
fa[v.to]=u;
dfs1(v.to);
if(siz[son[u]]<siz[v.to])son[u]=v.to;
siz[u]+=siz[v.to];
}
}
//若他在一条重链上,他的top就是第一个出现的点
//反之,他的top就是他自己
//求每个点的top
void dfs2(int u,int tp){
top[u]=tp;
if(son[u])dfs2(son[u],tp);//走重链
for(int i=0;i<g[u].size();i++){
Edge &v=g[u][i];
if(v.to==son[u]||v.to==fa[u])continue;
//若是一个重儿子,因为已经走过就不走了
//若是父亲当然也不走
dfs2(v.to,v.to);
}
}
int lca(int u,int v){
int fu=top[u];
int fv=top[v];
while(fu!=fv){
if(dep[fu]<dep[fv]){
swap(fu,fv);
swap(u,v);
}
//跳的是深度较深的
u=fa[fu];
fu=top[u];
}
if(dep[u]<dep[v])swap(u,v);
//返回的是深度小的那个
//因为他们这时的top相同,说明在一条重链上,深度较浅的那个是lca
return v;
}
int main() {
int n;
scanf("%d",&n);
for(int i=0;i<n-1;i++){
int u,v,val;
scanf("%d%d%d",&u,&v,&val);
g[u].push_back((Edge){v,val});
g[v].push_back((Edge){u,val});
}
dep[1]=1;
dfs1(1);
cout<<"a";
dfs2(1,1);
cout<<"b";
while(1){
int u,v;
cin>>u>>v;
cout<<lca(u,v);
}
return 0;
}
第二种:倍增求LCA
一:实现必需品
- dp存储这个点向上跳2的k次方步之后的点,这个点的祖先
- dep存储点的深度
二:实现步骤
- 首先把深度预处理出来
- 把两个点的深度跳到相同,再一起往上跳,直到跳到相同深度
- 求LCA
三:代码
#include <iostream>
int dp[maxn][20];
int dep[maxn];
//首先把深度跳到相同,再一起往上跳 ,直到跳相同的深度
void dfs(int u,int fa,int h){
dep[u]=h;
int t=0;
while(dp[u][t]){
dp[u][t+1]=dp[dp[u][t]][t];
t++;
}
for(int i=0;i<g[u].size();i++){
int now=g[u][i];
if(now==fa)continue;
dp[now][0]=u;
dfs(now,u,h+1);
}
}
int lca(int u,int v){
if(dep[u]<dep[v])swap(u,v);
//先跳到相同节点
for(int i=19;i>=0;i--){
//根节点的深度要为1,不然他们可能跳过头
if(dep[dp[u][i]]>=dep[v]){
u=dp[u][i];
}
}
if(u==v)return u;
for(int i=19;i>=0;i--){
if(dp[u][i]^dp[v][i])u=dp[u][i],v=dp[v][i];
//两个相同的数异或起来等于0
//看他们的公共祖先
}
return dp[u][0];
}
int main() {
scanf("%d%d",&n,&m);
for(int i=0;i<n-1;i++){
int a,b;
scanf("%d%d",&a,&b);
g[a].push_back(b);
g[b].push_back(a);
}
return 0;
}