详细讲解博客:http://dongxicheng.org/structure/lca-rmq/(没有代码)
求LCA(最近公共祖先)的算法有好多,按在线和离线分为在线算法和离线算法。
离线算法有基于搜索的Tarjan算法比较好,而在线算法则是基于dp的ST算法比较好。当然树链剖分也能写
先是ST算法。
这个算法是基于RMQ(区间最大最小值编号)的,而求LCA就是把树通过深搜得到一个序列,然后转化为求区间的最小编号。
参考博客:
http://blog.youkuaiyun.com/liangzhaoyang1/article/details/52549822
来两道例题:
1.hdu2586
题意:
一个村庄有 n 个房子和 n-1 条双向路,每两个房子之间都有一条简单路径。
现在有m次询问,求两房子之间的距离。
思路:
可以用LCA来解,首先找到u, v 两点的lca,然后计算一下距离值就可以了。
计算方法是,记下根结点到任意一点的距离dis[i],
这样ans = dis[u] + dis[v] - 2 * dis[lca(u, v)]了
代码:
#include <bits/stdc++.h>
using namespace std;
#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define min3(a, b, c) min(a, min(b, c))
#define max3(a, b, c) max(a, max(b, c))
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define dbg(x) cout << #x << "= " << x << endl;
typedef pair <int, int> pii;
const int inf = 0x3f3f3f3f;
const ll INF = (1LL<<63)-1;
const int N = 4e4+5;
const int M = 25;
int dp[N<<1][M]; // //这个数组记得开到2*N,因为遍历后序列长度为2*n-1
struct Edge{
int to, next, w;
}edge[N<<1];
int tol, head[N];
void init(){
tol = 0;
memset(head, -1, sizeof(head));
}
void addedge(int u, int v, int w){
edge[tol].to = v; edge[tol].w = w; edge[tol].next = head[u]; head[u] = tol++;
}
int ver[N<<1], R[N<<1], first[N], dis[N], cnt;
//ver:节点编号 R:深度 first:点编号位置 dis:距离 cnt:遍历后序列长度
void dfs(int u, int pre, int dep){
ver[++cnt] = u; first[u] = cnt; R[cnt] = dep;
for(int i=head[u]; i!=-1; i=edge[i].next){
int v = edge[i].to;
if(v == pre)continue;
int w = edge[i].w;
dis[v] = dis[u]+w;
dfs(v, u, dep+1);
ver[++cnt] = u;
R[cnt] = dep;
}
}
int rmq_init(int n){
for(int i=1; i<=n; i++)dp[i][0] = i;
for(int j=1; (1<<j)<=n; j++)
for(int i=1; i+(1<<j)-1<=n; i++){
int a = dp[i][j-1] , b = dp[i+(1<<(j-1))][j-1];
dp[i][j] = R[a]<R[b]?a:b;
}
}
//查询最小值,中间部分是交叉的
int rmq(int l,int r){
int k=(int)(log(r-l+1.0)/log(2.0));
int a = dp[l][k], b = dp[r-(1<<k)+1][k]; //返回其在ver数组中的下标
return R[a]<R[b]?a:b;
}
int LCA(int u, int v){
int x = first[u], y = first[v];
if(x > y)swap(x, y);
int ret = rmq(x, y);
return ver[ret];
}
int in[N];
int main(){
int T;
scanf("%d", &T);
while(T--){
init();
mst(in, 0);
int n, q;
scanf("%d%d", &n, &q);
for(int i=1; i<n; i++){
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
addedge(u, v, w);
addedge(v, u, w);
in[v]++;
}
int root;
for(int i=1; i<=n; i++)if(!in[i])root = i;
dis[root] = 0; cnt = 0;
dfs(root, -1, 1);
rmq_init(2*n-1);
while(q--){
int u, v;
scanf("%d%d", &u, &v);
int lca = LCA(u, v);
printf("%d\n", dis[u]+dis[v]-2*dis[lca]);
}
}
return 0;
}
2.POJ1330 Nearest Common Ancestors
就是直接求最近公共祖先
代码:
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <math.h>
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define min3(a, b, c) min(a, min(b, c))
#define max3(a, b, c) max(a, max(b, c))
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define dbg(x) cout << #x << "= " << x << endl;
typedef pair <int, int> pii;
const int inf = 0x3f3f3f3f;
const ll INF = (1LL<<63)-1;
const int N = 1e4+5;
const int M = 25;
int dp[N<<1][M]; // //这个数组记得开到2*N,因为遍历后序列长度为2*n-1
struct Edge{
int to, next, w;
}edge[N<<1];
int tol, head[N];
void init(){
tol = 0;
memset(head, -1, sizeof(head));
}
void addedge(int u, int v, int w){
edge[tol].to = v; edge[tol].w = w; edge[tol].next = head[u]; head[u] = tol++;
}
int ver[N<<1], R[N<<1], first[N], dis[N], cnt;
//ver:节点编号 R:深度 first:点编号位置 dis:距离 cnt:遍历后序列长度
void dfs(int u, int pre, int dep){
ver[++cnt] = u; first[u] = cnt; R[cnt] = dep;
for(int i=head[u]; i!=-1; i=edge[i].next){
int v = edge[i].to;
if(v == pre)continue;
int w = edge[i].w;
dis[v] = dis[u]+w;
dfs(v, u, dep+1);
ver[++cnt] = u;
R[cnt] = dep;
}
}
int rmq_init(int n){
for(int i=1; i<=n; i++)dp[i][0] = i;
for(int j=1; (1<<j)<=n; j++)
for(int i=1; i+(1<<j)-1<=n; i++){
int a = dp[i][j-1] , b = dp[i+(1<<(j-1))][j-1];
dp[i][j] = R[a]<R[b]?a:b;
}
}
//查询最小值,中间部分是交叉的
int rmq(int l,int r){
int k=(int)(log(r-l+1.0)/log(2.0));
int a = dp[l][k], b = dp[r-(1<<k)+1][k]; //返回其在ver数组中的下标
return R[a]<R[b]?a:b;
}
int LCA(int u, int v){
int x = first[u], y = first[v];
if(x > y)swap(x, y);
int ret = rmq(x, y);
return ver[ret];
}
int in[N];
int main(){
int T;
scanf("%d", &T);
while(T--){
init();
mst(in, 0);
int n;
scanf("%d", &n);
for(int i=1; i<n; i++){
int u, v, w;
scanf("%d%d", &u, &v);
addedge(u, v, 1);
addedge(v, u, 1);
in[v]++;
}
int root;
for(int i=1; i<=n; i++)if(!in[i])root = i;
dis[root] = 0; cnt = 0;
dfs(root, -1, 1);
rmq_init(2*n-1);
int u, v;
scanf("%d%d", &u, &v);
printf("%d\n", LCA(u, v));
}
return 0;
}
树链剖分的做法:
参考博客:
http://www.cnblogs.com/qilinart2/articles/5931595.html
http://www.cnblogs.com/fuyun-boy/p/6045709.html
代码:
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <math.h>
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define min3(a, b, c) min(a, min(b, c))
#define max3(a, b, c) max(a, max(b, c))
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define dbg(x) cout << #x << "= " << x << endl;
typedef pair <int, int> pii;
const int inf = 0x3f3f3f3f;
const ll INF = (1LL<<63)-1;
const int N = 1e4+5;
int n, m, q;
struct Edge{
int to, next;
}edge[N<<1];
int head[N], tol;
int top[N];//top[v]表示v所在的重链的顶端节点
int fa[N];//父亲节点
int dep[N];//深度
int sz[N];//sz[v]表示以v为根的子树的节点数
int p[N];//p[v]表示v与其父亲节点的连边在线段树中的位置
int fp[N];//和p数组相反 已知v在线段树中位置,其父亲节点是u
int son[N];//重儿子
int pos;
int a[N];
void init(){
tol = 0;
memset(head, -1, sizeof(head));
pos = 0;
memset(son, -1, sizeof(son));
}
void addedge(int u, int v){
edge[tol].to = v;edge[tol].next = head[u]; head[u] = tol++;
}
void dfs1(int u, int pre, int d){//第一遍dfs求出fa,deep,sz,son
dep[u] = d;
fa[u] = pre;
sz[u] = 1;
for(int i=head[u]; i!=-1; i=edge[i].next){
int v = edge[i].to;
if(v != pre){
dfs1(v, u, d+1);
sz[u] += sz[v];
if(son[u] == -1 || sz[v] > sz[son[u]])
son[u] = v;
}
}
}
void getpos(int u, int sp){ //第二遍dfs求出top和p
top[u] = sp;
p[u] = ++pos; //树状数组编号从1开始
fp[p[u]] = u;
if(son[u] == -1)return ;
getpos(son[u], sp);
for(int i=head[u]; i!=-1; i=edge[i].next){
int v = edge[i].to;
if(v != son[u] && v != fa[u])
getpos(v, v); //轻儿子
}
}
int LCA(int x, int y){
//如果两个的链顶不相等,我们就把他们往一起靠
//看那个点的链顶深度大改变那个点
for( ; top[x]!=top[y] ; dep[top[x]]>dep[top[y]]?x=fa[top[x]]:y=fa[top[y]]);
return dep[x]<dep[y]?x:y; //返回时要返回深度比较小的数
}
int in[N];
int main(){
int T;
scanf("%d", &T);
while(T--){
init();
mst(in, 0);
int n;
scanf("%d", &n);
for(int i=1; i<n; i++){
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
in[v]++;
}
int root;
for(int i=1; i<=n; i++)if(!in[i])root = i;
dfs1(root, -1, 0);
getpos(root, root);
int u, v;
scanf("%d%d", &u, &v);
printf("%d\n", LCA(u, v));
}
return 0;
}
3.洛谷 3379
树剖求LCA板子题
代码:
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <math.h>
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define min3(a, b, c) min(a, min(b, c))
#define max3(a, b, c) max(a, max(b, c))
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define dbg(x) cout << #x << "= " << x << endl;
typedef pair <int, int> pii;
const int inf = 0x3f3f3f3f;
const ll INF = (1LL<<63)-1;
const int N = 5e5+5;
int n, m, q;
struct Edge{
int to, next;
}edge[N<<1];
int head[N], tol;
int top[N];//top[v]表示v所在的重链的顶端节点
int fa[N];//父亲节点
int dep[N];//深度
int sz[N];//sz[v]表示以v为根的子树的节点数
int p[N];//p[v]表示v与其父亲节点的连边在线段树中的位置
int fp[N];//和p数组相反 已知v在线段树中位置,其父亲节点是u
int son[N];//重儿子
int pos;
int a[N];
void init(){
tol = 0;
memset(head, -1, sizeof(head));
pos = 0;
memset(son, -1, sizeof(son));
}
void addedge(int u, int v){
edge[tol].to = v;edge[tol].next = head[u]; head[u] = tol++;
}
void dfs1(int u, int pre, int d){//第一遍dfs求出fa,deep,sz,son
dep[u] = d;
fa[u] = pre;
sz[u] = 1;
for(int i=head[u]; i!=-1; i=edge[i].next){
int v = edge[i].to;
if(v != pre){
dfs1(v, u, d+1);
sz[u] += sz[v];
if(son[u] == -1 || sz[v] > sz[son[u]])
son[u] = v;
}
}
}
void getpos(int u, int sp){ //第二遍dfs求出top和p
top[u] = sp;
p[u] = ++pos; //树状数组编号从1开始
fp[p[u]] = u;
if(son[u] == -1)return ;
getpos(son[u], sp);
for(int i=head[u]; i!=-1; i=edge[i].next){
int v = edge[i].to;
if(v != son[u] && v != fa[u])
getpos(v, v); //轻儿子
}
}
int LCA(int x, int y){
//如果两个的链顶不相等,我们就把他们往一起靠
//看那个点的链顶深度大改变那个点
for( ; top[x]!=top[y] ; dep[top[x]]>dep[top[y]]?x=fa[top[x]]:y=fa[top[y]]);
return dep[x]<dep[y]?x:y; //返回时要返回深度比较小的数
}
int main(){
init();
int n, m, s;
scanf("%d%d%d", &n, &m, &s);
for(int i=1; i<n; i++){
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
}
dfs1(s, -1, 0);
getpos(s, s);
while(m--){
int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", LCA(x, y));
}
return 0;
}