- lca:树上两点的最近公共祖先
倍增算法(在线)
- 利用dfs记录结点深度deep[i],并求出单点祖先:f[i][j]:从结点i向上出发
2j
步的祖先,可推出→
f[i][0] = fa[i], f[i][j] = f[f[i][j - 1]][j - 1] - 求两点x,y的lca,不妨令deep[x] >= deep[y],x先爬deep[x]-deep[y]步使得x和y的deep相同;若此时x == y,则x就是最近公共祖先;否则x和y同时爬一些步数后,若到达的点相同,则找到公共祖先,第一个这样的点,就是最近公共祖先。
- 代码:
inline void dfs(int u, int fa)
{
deep[u] = deep[fa] + 1;
f[u][0] = fa;
for(int i = 1; i <= 20 && f[u][i - 1] > 0; ++ i)
f[u][i] = f[f[u][i - 1]][i - 1];
for(int i = head[u]; i; i = e[i].next)
if (e[i].to != fa) dfs(e[i].to, u);
}
inline int lca(int x, int y)
{
if (deep[x] < deep[y]) swap(x, y);
for(int i = 20; i >= 0; -- i)
if (deep[f[x][i]] >= deep[y]) x = f[x][i];
if (x == y) return x;
for(int i = 20; i >= 0; -- i)
if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
- 时间复杂度O(n * log n + q * log n)
RMQ算法(在线)
- dfs并用数组a[0]按时间戳递增记录访问与回溯的结点,用a[1]记录a[0]中结点的深度,并用fir数组标记每一个点在a[0]中第一次出现的时间。
如:【手动画图莫嫌弃QAQ
对于上图中的树,
a[0] = {1, 2, 4, 2, 5, 6, 5, 7, 5, 2, 1, 3, 8, 3, 1};
a[1] = {0, 1, 2, 1, 2, 3, 2, 3, 2, 1, 0, 1, 2, 1, 0};
fir = {1, 2, 12, 3, 5, 6, 8, 13}; - 经过推敲发现,对于两个点x,y的lca为a[1]中(fir[x], fir[y])的最小值所对应a[0]的点,可用RMQ快速查询。
- 代码略去。因与倍增同为在线,我较少使用【但貌似效率较高。推荐一版代码。
Tajan算法(离线)
- Tarjan算法基于从根节点开始DFS时,在刚刚好访问完两个节点时,这两个节点的LCA必定没有在DFS中回溯。
- 当访问完x的每棵子树时,用并查集把这些子树记为与x同集。
- 可发现当dfs访问到v时,u以访问,lca(u, v) = find(v)。【建议手模一下】
- 代码:
inline int find(int x)
{
return p[x] == x ? x : p[x] = find(p[x]);
}
inline void dfs(int u, int fa)
{
vi[u] = true;
for(int i = head[u]; i; i = e[i].next)
if (e[i].to != fa)
{
int v = e[i].to;
dfs(v, u);
p[v] = u;
}
for(int i = qh[u]; i; i = q[i].next)
if (vi[q[i].to]) ans[q[i].id] = find(q[i].to);
}
- 时间复杂度:O(n + q)
答案便于记录时,个人感觉tajan好用,简短而快速。