LCA之倍增法(例题:POJ1330)

LCA之倍增法

最近公共祖先简称 LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。

除去朴素算法,LCA还可以用倍增法来求,倍增法在算法中的应用还是比较多的,可以用来求ST表(区间最大最小值),还可以用来求LCA。其中求LCA的方法如下:

整体流程

预处理

用BFS或者DFS处理

  1. deg[i]:结点i的深度
  2. fa[i][j]:每个结点往上跳2^j后的位置
  3. tu,tv为跳到的位置

计算

假设求u和v的LCA,核心就只有两步

  1. 将u和v先跳到同一层
  2. u和v同时往上跳
  3. 跳的时候总是跳2 ^ j个,从大到小跳(判断能不能跳,能跳就跳)
  4. 最后返回fa[tu][0]

原理

我们假设u跳到LCA的距离为Len,因为Len可以用二进制表示,也就是说Len可以拆分为很多个2的倍数,所以我们每次跳2的j次个一定可以跳到LCA,同时我们跳的次数的复杂度就是logn的复杂度。
关于判断我们选择的j次是不是要跳的,我们可以判断跳了2的j次以后所在的位置,如果他俩相等说明跳多了,因为我们最后的结果是跳到LCA的下一个,所以他们一定不会相等,相等即说明跳多了。
看一下模板里面的具体实现可以更加清楚一点。

模板

copy from kuangbin

const int MAXN=10010;
// 跳转的大小,可以手动调大
const int DEG=20;

// 链式前向星部分
struct Edge{
    int to,next;
}edge[MAXN*2];
int head[MAXN],tot;
void addedge(int u,int v) {
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
void init() {
    tot=0;
    memset(head,-1,sizeof(head));
}

// fa[i][j]表示结点i的第2^j个祖先
int fa[MAXN][DEG];
// 深度数组
int deg[MAXN];

// 标记深度,预处理fa数组
void BFS(int root) {
    queue<int>que;
    deg[root]=0;
    fa[root][0]=root;
    que.push(root);
    while(!que.empty()) {
        int tmp=que.front();
        que.pop();
        for (int i=1;i<DEG;i++)
            fa[tmp][i]=fa[fa[tmp][i-1]][i-1];
        for (int i=head[tmp];i!=-1;i=edge[i].next) {
            int v=edge[i].to;
            if (v==fa[tmp][0]) continue;
            deg[v]=deg[tmp]+1;
            fa[v][0]=tmp;
            que.push(v);
        }
    }
}

int LCA(int u,int v) {
    // 始终令u的深度比较小
    if (deg[u]>deg[v]) swap(u,v);
    int hu=deg[u],hv=deg[v];
    int tu=u,tv=v;
    // 第一步:将v跳到和u同一层
    for (int det=hv-hu,i=0;det;det>>=1,i++)
        if (det&1)
            tv=fa[tv][i];
    if (tu==tv) return tu;
    // 第二步:u和v同时跳
    for (int i=DEG-1;i>=0;i--) {
        // 跳多了,则不跳
        if (fa[tu][i]==fa[tv][i]) continue;
        tu=fa[tu][i];
        tv=fa[tv][i];
    }
    return fa[tu][0];
}

例题:Nearest Common Ancestors(POJ1330)

Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 39659 Accepted: 19657

Description

A rooted tree is a well-known data structure in computer science and engineering. An example is shown below:

In the figure, each node is labeled with an integer from {1, 2,…,16}. Node 8 is the root of the tree. Node x is an ancestor of node y if node x is in the path between the root and node y. For example, node 4 is an ancestor of node 16. Node 10 is also an ancestor of node 16. As a matter of fact, nodes 8, 4, 10, and 16 are the ancestors of node 16. Remember that a node is an ancestor of itself. Nodes 8, 4, 6, and 7 are the ancestors of node 7. A node x is called a common ancestor of two different nodes y and z if node x is an ancestor of node y and an ancestor of node z. Thus, nodes 8 and 4 are the common ancestors of nodes 16 and 7. A node x is called the nearest common ancestor of nodes y and z if x is a common ancestor of y and z and nearest to y and z among their common ancestors. Hence, the nearest common ancestor of nodes 16 and 7 is node 4. Node 4 is nearer to nodes 16 and 7 than node 8 is.

For other examples, the nearest common ancestor of nodes 2 and 3 is node 10, the nearest common ancestor of nodes 6 and 13 is node 8, and the nearest common ancestor of nodes 4 and 12 is node 4. In the last example, if y is an ancestor of z, then the nearest common ancestor of y and z is y.

Write a program that finds the nearest common ancestor of two distinct nodes in a tree.

Input

The input consists of T test cases. The number of test cases (T) is given in the first line of the input file. Each test case starts with a line containing an integer N , the number of nodes in a tree, 2<=N<=10,000. The nodes are labeled with integers 1, 2,…, N. Each of the next N -1 lines contains a pair of integers that represent an edge --the first integer is the parent node of the second integer. Note that a tree with N nodes has exactly N - 1 edges. The last line of each test case contains two distinct integers whose nearest common ancestor is to be computed.

Output

Print exactly one line for each test case. The line should contain the integer that is the nearest common ancestor.

Sample Input

2
16
1 14
8 5
10 16
5 9
4 6
8 4
4 10
1 13
6 15
10 11
6 7
10 2
16 3
8 1
16 12
16 7
5
2 3
3 4
3 1
1 5
3 5

Sample Output

4
3

Source

POJ1330

分析:

LCA板子题,见代码(kuangbin的)。

#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;

const int MAXN=10010;
// 跳转的大小,可以手动调大
const int DEG=20;

// 链式前向星部分
struct Edge{
    int to,next;
}edge[MAXN*2];
int head[MAXN],tot;
void addedge(int u,int v) {
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
void init() {
    tot=0;
    memset(head,-1,sizeof(head));
}

// fa[i][j]表示结点i的第2^j个祖先
int fa[MAXN][DEG];
// 深度数组
int deg[MAXN];

// 标记深度,预处理fa数组
void BFS(int root) {
    queue<int>que;
    deg[root]=0;
    fa[root][0]=root;
    que.push(root);
    while(!que.empty()) {
        int tmp=que.front();
        que.pop();
        for (int i=1;i<DEG;i++)
            fa[tmp][i]=fa[fa[tmp][i-1]][i-1];
        for (int i=head[tmp];i!=-1;i=edge[i].next) {
            int v=edge[i].to;
            if (v==fa[tmp][0]) continue;
            deg[v]=deg[tmp]+1;
            fa[v][0]=tmp;
            que.push(v);
        }
    }
}

int LCA(int u,int v) {
    // 始终令u的深度比较小
    if (deg[u]>deg[v]) swap(u,v);
    int hu=deg[u],hv=deg[v];
    int tu=u,tv=v;
    // 将v跳到和u同一层
    for (int det=hv-hu,i=0;det;det>>=1,i++)
        if (det&1)
            tv=fa[tv][i];
    if (tu==tv) return tu;
    // u和v同时跳
    for (int i=DEG-1;i>=0;i--) {
        // 跳多了,则不跳
        if (fa[tu][i]==fa[tv][i]) continue;
        tu=fa[tu][i];
        tv=fa[tv][i];
    }
    return fa[tu][0];
}

bool flag[MAXN];
int main() {
    int T;
    int n;
    int u,v;
    scanf("%d",&T);
    while(T--) {
        scanf("%d",&n);
        init();
        memset(flag,false,sizeof(flag));
        for (int i=1;i<n;i++) {
            scanf("%d%d",&u,&v);
            addedge(u,v);
            addedge(v,u);
            flag[v]=true;
        }
        int root;
        for (int i=1;i<n;i++) {
            if (!flag[i]) {
                root=i;
                break;
            }
        }
        BFS(root);
        scanf("%d%d",&u,&v);
        printf("%d\n",LCA(u,v));
    }
    return 0;
}

发布了136 篇原创文章 · 获赞 33 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Radium_1209/article/details/102557477