树的最近公共祖先问题,是指给定一棵有向树(无向的可以转化为有向的),对于询问q(u,y),返回u和v的最近公共祖先。
解决问题算法可以分为在线算法和离线算法。
离线算法思想:
离线算法需要预先将所有的询问存起来,等到dfs的时候一下逐一输出结果。
dfs对树进行遍历,当某个节点u的一个子树全部遍历完以后,就更新这棵子树的节点的父节点为u(转化为并查集:将该子树集合加入到u所在的集合中,并更新子树集合的父亲为u)如果当前节点x是被询问到的节点,并且一直相关的(询问了x,y的最近公共祖先)另一个节y点已经搜索过,则x与y的最近公共祖先为y此时的父亲。
在线算法的思想:
在线算法的思想比较简单,先对整棵树进行dfs,并记下每个节点的深度以及父节点,当询问某对节点时,比较两个节点的深度,深度大的节点开始沿着父节点往回走,当两个节点的深度相同时,判断其是否为同一个节点,若是,则输出,否则,两节点同时沿着各自的父亲节点往回走,直到两节点相同,输出。
个比较喜欢用在线算法,而且优化后速度也很快。
刚开始学习这个算法的时候也在网上找了许多资料,但是还是觉得这片文章写得很好;
http://blog.csdn.net/v_july_v/article/details/18312089
看完算法后,我自己也找了几道简单的题目做了做;
poj1330 离线算法
#include<cstdio>
#include<cstring>
#include<vector>
#define MAX 10010
using namespace std;
vector<int>G[MAX];
int fa[MAX],lca[MAX],c[MAX],vis[MAX],u,v,ans;
int find(int x){ //并查集
return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
void merge(int x, int y){
x = find(x);
y = find(y);
if (x != y)
fa[x] = y;
}
void LCA(int root){ //最近公共祖先
for (int i = 0; i<G[root].size(); i++){
LCA(G[root][i]);
merge(G[root][i],fa[root]); //如何合并?
}
vis[root] = 1;
if (root == u && vis[v]){
printf("%d\n",find(v));
return;
}
else if (root == v && vis[u]){
printf("%d\n",find(u));
return;
}
}
int main(){
int T,n,root;
scanf("%d",&T);
while (T--){
scanf("%d",&n); //初始化并输入数据
for (int i = 0; i<MAX; i++) G[i].clear();
memset(c,0,sizeof(c));
memset(vis,0,sizeof(vis));
for (int i = 1; i<=n; i++){
fa[i] = i;
lca[i] = i;
}
int a,b;
for (int i = 0; i<n-1; i++){
scanf("%d%d",&a,&b);
c[b]++;
G[a].push_back(b);
}
for (int i = 1; i<=n; i++) if (c[i] == 0){ //找到根节点
root = i;
break;
}
scanf("%d%d",&u,&v);
LCA(root);
}
return 0;
}
hdu2586在线算法;
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define MAX 40010
using namespace std;
struct Edge{
int from,to,dist;
};
vector<int>G[MAX];
vector<Edge>edges;
int D[MAX], d[MAX], fa[MAX], in[MAX];
bool vis[MAX];
void init(){
edges.clear();
for (int i=0; i<MAX; i++) G[i].clear();
memset(vis,false,sizeof(vis));
memset(d,0,sizeof(d));
memset(in,0,sizeof(in));
}
void AddEdge(int from, int to, int dist){
edges.push_back((Edge){from,to,dist});
int k = edges.size();
G[from].push_back(k-1);
}
void dfs(int pa, int u, int depth){
D[u] = depth;
vis[u] = true;
fa[u] = pa;
for (int i=0; i<G[u].size(); i++){
Edge& e = edges[G[u][i]];
if (vis[e.to]) continue;
d[e.to] = d[u] + e.dist;
dfs(u,e.to,depth+1);
}
}
int LCA(int u, int v){
int x = D[u];
int y = D[v];
if (x < y) {
swap(u,v);
swap(x,y);
}
while (x > y){
u = fa[u];
x--;
}
if (u == v) return v;
while (u != v){
u = fa[u];
v = fa[v];
}
return v;
}
int main(){
int T,n,m;
scanf("%d",&T);
while (T--){
init();
scanf("%d%d",&n,&m);
int x,y,z;
for (int i = 1; i<n; i++){
scanf("%d%d%d",&x,&y,&z);
in[y]++;
AddEdge(x,y,z);
}
int root;
for (int i = 1; i<=n; i++) if (!in[i]){
root = i;
break;
}
dfs(-1,root,0);
// for (int i = 1; i<=n; i++) printf("%d ",D[i]); printf("\n");
int u,v;
for (int i = 0; i<m; i++){
scanf("%d%d",&u,&v);
int w = LCA(u,v);
printf("%d\n",d[u]+d[v]-2*d[w]);
}
}
return 0;
}
hdu2586在线算法 + ST优化;
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define MAX 80010
using namespace std;
struct Edge{
int form, to, dist;
};
int E[MAX],D[MAX],R[MAX],in[MAX],dp[MAX][30],d[MAX],cnt; //cnt = -1
bool vis[MAX];
vector<int>G[MAX];
vector<Edge>edges;
void AddEdge(int from, int to, int dist){
edges.push_back((Edge){from,to,dist});
int k = edges.size();
G[from].push_back(k-1);
}
void dfs(int fa, int u, int depth){
E[++cnt] = u;
D[u] = depth;
R[u] = cnt;
vis[u] = true;
for (int i = 0; i<G[u].size(); i++){
Edge& e = edges[G[u][i]];
if (vis[e.to]) continue;
d[e.to] = d[u] + e.dist;
dfs(u,e.to,depth+1);
E[++cnt] = u;
}
}
void RMQ(){
for (int i = 0; i < cnt; i++) dp[i][0] = E[i];
for (int j = 1; (1<<j) < cnt; j++){
for (int i = 0; i<cnt-(1<<j)+1; i++){
if (D[dp[i][j-1]] <= D[dp[i+(1<<(j-1))][j-1]])
dp[i][j] = dp[i][j-1];
else
dp[i][j] = dp[i+(1<<(j-1))][j-1];
}
}
}
int LCA(int u, int v){
u = R[u];
v = R[v];
if (u > v) swap(u,v);
int k = 0;
while ((1<<k) <= (v-u+1)) k++;
k--;
if (D[dp[u][k]] < D[dp[v-(1<<k)+1][k]]) return dp[u][k];
return dp[v-(1<<k)+1][k];
}
int main(){
int T,n,m;
scanf("%d",&T);
while (T--){
edges.clear();
for (int i = 0; i<MAX; i++) G[i].clear();
memset(vis,false,sizeof(vis));
memset(in,0,sizeof(in));
cnt = -1;
scanf("%d%d",&n,&m);
int x,y,z;
for (int i = 1; i<n; i++){
scanf("%d%d%d",&x,&y,&z);
in[y]++;
AddEdge(x,y,z);
}
int root;
for (int i = 1; i<=n; i++) if (!in[i]){
root = i;
break;
}
dfs(-1,root,0);
//for (int i = 1; i<=n; i++) printf("%d ",d[i]); printf("\n");
cnt += 1;
RMQ();
int u, v;
for (int i = 0; i<m; i++){
scanf("%d%d",&u,&v);
int w = LCA(u,v);
printf("%d\n",d[u] + d[v] - 2*d[w]);
}
}
return 0;
}