题目链接:
POJ 1330 Nearest Common Ancestors
题意:
给一个
n
点和
数据范围:
分析:
可以学习《挑战程序设计竞赛》
P328−P331
暴力求解
记节点
v
到根的深度为
时间复杂度:
dfs
:
O(n)
,单次查询:
O(n)
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAX_N = 10010;
int T, n, total, root;
int head[MAX_N], in[MAX_N], fa[MAX_N], depth[MAX_N];
struct Edge {
int v, next;
}edge[MAX_N];
void AddEdge(int u, int v)
{
edge[total].v = v;
edge[total].next = head[u];
head[u] = total++;
}
void dfs(int u, int p, int cur)
{
fa[u] = p;
depth[u] = cur;
for (int i = head[u]; i != -1; i = edge[i].next) {
dfs (edge[i].v, u, cur + 1);
}
}
int LCA(int u, int v)
{
while (depth[u] > depth[v]) u = fa[u];
while (depth[v] > depth[u]) v = fa[v];
while (u != v) {
u = fa[u];
v = fa[v];
}
return u;
}
int main()
{
scanf("%d", &T);
while (T--) {
total = 0;
memset(head, -1, sizeof (head));
memset(in, 0, sizeof (in));
scanf("%d", &n);
int u, v;
for (int i = 1; i <= n; ++i) {
scanf("%d%d", &u, &v);
if (i != n) {
AddEdge(u, v);
in[v]++;
}
}
for (int i = 1; i <= n; ++i) {
if (in[i] == 0) {
root = i;
break;
}
}
dfs (root, -1, 0);
printf("%d\n", LCA(u, v));
}
return 0;
}
二分搜索
首先对于任意节点
v
,利用其父节点
预处理
parent[v][k]
:
O(nlogn)
,单次查询:
O(logn)
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;
typedef long long ll;
const int MAX_N = 10010;
const int MAX_LOG_N = 20; // MAX_LOG_N = log2(MAX_N)
int T, n, total, root;
int head[MAX_N], in[MAX_N], depth[MAX_N], fa[MAX_N][MAX_LOG_N];
struct Edge {
int v, next;
}edge[MAX_N];
void AddEdge(int u, int v)
{
edge[total].v = v;
edge[total].next = head[u];
head[u] = total++;
}
void dfs(int u, int p, int d)
{ // 获取每个节点的深度和直接父亲
depth[u] = d;
fa[u][0] = p;
for (int i = head[u]; i != -1; i = edge[i].next) {
dfs(edge[i].v, u, d + 1);
}
}
void RMQ() // 时间复杂度: O(nlog(n))
{ // 倍增处理每个节点的祖先
for (int k = 0; k + 1 < MAX_LOG_N; ++k) {
for (int i = 1; i <= n; ++i) {
if (fa[i][k] == -1) fa[i][k + 1] = -1;
else fa[i][k + 1] = fa[fa[i][k]][k];
}
}
}
int LCA(int u, int v) // 时间复杂度:O(log(n))
{
// 先把两个节点提到同一深度
if (depth[u] > depth[v]) swap(u, v);
for (int k = 0; k < MAX_LOG_N; ++k) {
if (((depth[v] - depth[u]) >> k) & 1) {
v = fa[v][k];
}
}
if (u == v) return v;
// 二分搜索计算LCA
for (int k = MAX_LOG_N - 1; k >= 0; --k) {
if (fa[u][k] != fa[v][k]) {
u = fa[u][k];
v = fa[v][k];
}
}
return fa[u][0];
}
int main()
{
scanf("%d", &T);
while (T--) {
memset(head, -1, sizeof (head));
memset(in, 0, sizeof(in));
memset(depth, 0, sizeof(depth));
memset(fa, -1, sizeof(fa));
total = 0;
scanf("%d", &n);
int u, v;
for (int i = 1; i <= n; ++i) {
scanf("%d%d", &u, &v);
if (i != n) {
AddEdge(u, v);
in[v]++;
}
}
// 找到根节点
for (int i = 1; i <= n; ++i) {
if (in[i] == 0) {
root = i;
break;
}
}
dfs(root, -1, 0);
RMQ();
printf("%d\n", LCA(u, v));
}
return 0;
}
基于RMQ的算法
其实上面的二分搜索算法已经有了
RMQ
的意思了。
将树转为从根
DFS
标号后得到的序列处理。
首先按从根
DFS
访问的顺序得到顶点序列
vis[i]
和对应的深度
depth[i]
。对于每个顶点
v
,记其在
预处理: O(nlogn) ,单次查询: O(1)
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAX_N = 10010;
int T, n, total;
int head[MAX_N], vis[MAX_N * 2], id[MAX_N], depth[MAX_N * 2], dp[MAX_N * 2][20], in[MAX_N];
struct Edge {
int v, next;
} edge[MAX_N * 2];
void AddEdge (int u, int v)
{
edge[total].v = v;
edge[total].next = head[u];
head[u] = total++;
}
void dfs(int u, int p, int d, int& k)
{
vis[k] = u; // dfs访问顺序
id[u] = k; // 节点在vis中首次出现的下标
depth[k++] = d; // 节点对应的深度
for (int i = head[u]; i != -1; i = edge[i].next) {
int v = edge[i].v;
if (v == p) continue;
dfs(v, u, d + 1, k); // 递归访问子节点
vis[k] = u; // 再次访问
depth[k++] = d; // 标记vis的深度
}
}
void RMQ(int root) // 处理区间深度最小值,保存最小值的下标
{ // 就是区间左右端点最近公共祖先的下标,即:vs[Min] = LCA
int k = 0;
dfs(root, -1, 0, k);
// printf ("k = %d\n", k);
int m = k; // m = 2 * n - 1
int e = (int)(log2(m + 1.0)); // 区间长度m + 1
for (int i = 0; i < m; ++i) dp[i][0] = i;
for (int j = 1; j <= e; ++j) {
for (int i = 0; i + (1 << j) - 1 < m; ++i) {
int nxt = i + (1 << (j - 1));
if (depth[dp[i][j - 1]] < depth[dp[nxt][j - 1]]) {
dp[i][j] = dp[i][j - 1];
} else {
dp[i][j] = dp[nxt][j - 1];
}
}
}
}
int LCA(int u, int v)
{
int left = min(id[u], id[v]), right = max(id[u], id[v]);
int k = (int)(log2(right - left + 1.0)); // 区间长度,注意用log2!
int pos, nxt = right - (1 << k) + 1; // nxt 分界点
if (depth[dp[left][k]] < depth[dp[nxt][k]]) {
pos = dp[left][k];
} else {
pos = dp[nxt][k];
}
return vis[pos];
}
int main()
{
scanf("%d", &T);
while (T--) {
memset(head, -1, sizeof(head));
memset(in, 0, sizeof(in));
total = 0;
int u, v;
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d%d", &u, &v);
if (i < n) {
AddEdge(u, v);
in[v]++;
}
}
int root;
for (int i = 1; i <= n; ++i) {
if (in[i] == 0) {
root = i;
break;
}
}
RMQ(root);
printf("%d\n", LCA(u, v));
}
return 0;
}