POJ 1330 Nearest Common Ancestors(LCA,在线处理三种方式)

来源:互联网 发布:我与网络强国演讲稿 编辑:程序博客网 时间:2024/06/03 21:10

题目链接:
POJ 1330 Nearest Common Ancestors
题意:
给一个n点和n1条边的树,第n行是要查询的两个节点的最近公共祖先,输出要查询的最近公共祖先。
数据范围:n104
分析:
可以学习《挑战程序设计竞赛》P328P331

暴力求解

记节点v到根的深度为depth[v]。那么如果节点wuv的公共祖先的话,让u向上走depth[u]depth[w]步,让v向上走depth[v]depth[w]步,都将走到w。因此,首先让uv中较深的一个向上走|depth[u]depth[v]|步,再一起一步步向上走,直到走到同一个节点即是LCA
时间复杂度: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][1]信息,可以通过parent[v][2]=parent[parent[v][1]][1]得到其向上走两步所到的节点,再利用这一信息,又可以通过parent[v][4]=parent[parent[v][2]][2]得到其向上走四步所到的节点。依此类推,就能够得到其向上走2k步所到的节点parent[v][k]。有了k=floor(log2(n))以内的所有信息后,就可以二分搜索了。
预处理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,记其在vis[]中首次出现的下标为id[v]。而LCA(u,v)就是访问u之后到访问v之前所经过的节点中离根最近的那个,假设id[u]id[v],那么有:

LCA(u,v)=vis[k],kid[u]iid[v]depth[i]i

预处理: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;}
0 0
原创粉丝点击