您的位置:首页 > 其它

LCA离线算法学习笔记

2017-04-11 15:11 246 查看

算法思想:

LCA离线,采用的是递归的tarjan算法,利用了树深度优先遍历的性质可以在一次遍历过程中巧妙的求解出查询的最接近公共祖先,时间复杂度是O(n+q),但前提是需要离线保存所有询问。

算法思路如下:

当前dfs遍历到节点u,先将vis[u]标记成true,然后处理以u为根节点的子树,子树处理完成后,将这棵子树中的所有点的祖先都设成u,这时候处理跟u相关的所有询问(u,v),如果vis[v]==true,那么lca(u,v)就等于v当前设置的祖先。

举个例子,示意图如下:



对于正确性,这里对于询问v考虑三种情况:

v属于u的子树,这时v已经被访问过,所以vis[v]==true。由于之前处理的了u的子节点的祖先都为u,所以v当前设置的祖先也是u,故lca(u,v)=u,很显然是正确的。

v属于u的祖先,既然是dfs过程,在访问到u之前,u的祖先一定已经访问过,所以vis[v]==true。因为当前处理的是v的子树u,所以以v为根的子树还没有被处理完全,故这时候v的祖先还是它自己,所以lca(u,v)=v,正确。

v属于u的兄弟节点或者u祖先的兄弟节点为根的子树中,这样v有可能被访问过,也有可能没有。没有访问过自然不需要处理,如果v被访问了,那么v当前设置的祖先是什么呢?以上图作为例子,假设u是5,v是3,在访问到u的时候,v已经被访问了,那么根据算法的步骤,此时v的祖先被设置成节点1,从图中观察,lca(u,v)确实就是节点1,故算法正确。

其实tarjan算法就是利用了树的dfs的性质,从一个节点v遍历到另一个节点u,肯定会经过lca(u,v),此时按照算法将v的祖先就设置成lca(u,v),就可以保证算法正确。至于寻找祖先的操作,很显然就利用并查集来优化查询速度。

例题:

还是用LCA入门题:HDU-2586

题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=2586

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 44444;

struct Node {
int v, w;
};

struct QNode {
int v, id;
};

struct Query {
int u, v, lca;
};

struct LCA {
int n, m;
vector <Node> tree[MAXN];
vector <QNode> qtree[MAXN];
Query q[MAXN];
int dist[MAXN], pa[MAXN];
bool vis[MAXN];

void init(int n, int m) {
this->n = n;
this->m = m;
memset(vis, false, sizeof(vis));
memset(dist, 0, sizeof(dist));
for (int i = 1; i <= n; i++) pa[i] = i;
for (int i = 1; i <= n; i++) tree[i].clear();
for (int i = 1; i <= m; i++) qtree[i].clear();
}

void addEdge(int u, int v, int w) {
tree[u].push_back((Node) {v, w});
tree[v].push_back((Node) {u, w});
}

void addQuEdge(int u, int v, int id) {
q[id] = (Query){u, v, -1};
qtree[u].push_back((QNode) {v, id});
qtree[v].push_back((QNode) {u, id});
}

int Find(int x) {
return pa[x] == x ? x : pa[x] = Find(pa[x]);
}

void tarjan(int u) {
vis[u] = true;
for (int i = 0; i < (int)tree[u].size(); i++) {
int v = tree[u][i].v, w = tree[u][i].w;
if (vis[v]) continue;
dist[v] = dist[u] + w;
tarjan(v);
pa[v] = u;
}
for (int i = 0; i < (int)qtree[u].size(); i++) {
int v = qtree[u][i].v, id = qtree[u][i].id;
if (vis[v]) {
q[id].lca = Find(v);
}
}
}

} Lca;

int main() {
//freopen("in.txt", "r", stdin);
int T;
scanf("%d", &T);
while (T--) {
int n, m;
scanf("%d%d", &n, &m);
Lca.init(n, m);
for (int i = 1; i < n; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
Lca.addEdge(u, v, w);
}
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
Lca.addQuEdge(u, v, i);
}
Lca.tarjan(1);
for (int i = 1; i <= m; i++) {
int u = Lca.q[i].u, v = Lca.q[i].v, lca = Lca.q[i].lca;
printf("%d\n", Lca.dist[u] + Lca.dist[v] - 2 * Lca.dist[lca]);
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  acm lca 离线