POJ 1330 Nearest Common Ancestors LCA--》RMQ or 纯DFS

Nearest Common Ancestors
Time Limit: 1000MS   Memory Limit: 10000K
Total Submissions: 22446   Accepted: 11720

Description

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

POJ 1330 Nearest Common Ancestors LCA--》RMQ or 纯DFS_第1张图片 
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

Taejon 2002

[Submit]   [Go Back]   [Status]   [Discuss]

Home Page   Go Back  To top

LCA最近公共祖先,给出两个结点问其最近公共祖先,可以转化为rmq求解。

我们用dfs深搜得到一个数列在记录下每次询问的深度和结点第一次出现的位置

然后就转为求区间最小值

如例子中

index 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

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

depth 0 1 2 1 2 1 0 1 2 3 2 3 2 3 4 3 4 3 2 1 2 3 2 3 2 1 0 1 2 1 0

first 2 10 17 8 28 21 22 1 29 9 12 15 3 5 24 14

搜索得到序列之后假如我们想求16和 7的 LCA

那么我们找16和7在序列中的位置通过first 数组查找发现在14---22

即  16  12  16   3  16  10   4   6   7在上面图上找发现正好是以4为根的子树。而我们只要找到其中一个深度最小的编号就可以了、

这时候我们就用到了RMQ算法。

维护一个dp数组保存其区间深度最小的下标,查找的时候返回就可以了。

比如上面我们找到深度最小的为4点,返回其编号即可。

///求log2的数组模板漏打一句debug半天  蒟蒻都这样

ACcode:

#pragma warning(disable:4786)//使命名长度不受限制
#pragma comment(linker, "/STACK:102400000,102400000")//手工开栈
#include <map>
#include <set>
#include <queue>
#include <cmath>
#include <stack>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
#define rd(x) scanf("%d",&x)
#define rd2(x,y) scanf("%d%d",&x,&y)
#define rds(x) scanf("%s",x)
#define rdc(x) scanf("%c",&x)
#define ll long long int
#define maxn 25000
#define mod 1000000007
#define INF 0x3f3f3f3f //int 最大值
#define FOR(i,f_start,f_end) for(int i=f_start;i<=f_end;++i)
#define MT(x,i) memset(x,i,sizeof(x))
#define PI  acos(-1.0)
#define E  exp(1)
using namespace std;
struct Edge{
    int v,next;
}edge[maxn];
int loop,n,cnt,e;
int head[maxn];
int first[maxn];///结点第一次出现的位置下标
int occur[maxn];///结点出现的顺序数组(有重复)
int depth[maxn];///结点在树中的深度与occur对应
int dp[maxn][20];///rmq数组求区间最小值
int mm[maxn];///log2 数组
bool flag[maxn];///记入结点是否入度
/*
入度(indegree):在有向图中,一个顶点v的入度
是指与该边相关联的入边(即边的尾是v)的条数
*/
void init(){
    MT(head,-1);
    MT(flag,false);
    MT(first,0);
    e=cnt=0;
}
void dfs(int u,int deep){
    occur[++cnt]=u;///进入该结点记录
    depth[cnt]=deep;
    first[u]=first[u]?first[u]:cnt;///记录第一次访问
    int i=head[u];
    while(i+1){
        dfs(edge[i].v,deep+1);
        occur[++cnt]=u;
        depth[cnt]=deep;
        i=edge[i].next;
    }
}
void RMQ_Init(int m){
    mm[0]=-1;
    FOR(i,1,m){
        dp[i][0]=i;///记录最小值的下标
        mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1]; ///log2数组
    }
    FOR(j,1,19)
        FOR(i,1,m)
            if(i+(1<<j)-1<=m)
                dp[i][j]=depth[dp[i][j-1]]<depth[dp[i+(1<<(j-1))][j-1]]?
                dp[i][j-1]:dp[i+(1<<(j-1))][j-1];
}
void fun(){
    init();
    rd(n);
    int u,v;
    FOR(i,1,n-1){
        rd2(u,v);
        ///加边
        edge[e].v=v;
        edge[e].next=head[u];
        head[u]=e++;
        flag[v]=true;
    }
    FOR(i,1,n)
        if(!flag[i]){///从根开始遍历
            dfs(i,0);
            break;
        }
    RMQ_Init(cnt);
}
void RMQ(int x,int y){
    int k=mm[y-x+1];
    k=depth[dp[x][k]]<depth[dp[y-(1<<k)+1][k]]?
        dp[x][k]:dp[y-(1<<k)+1][k];
    printf("%d\n",occur[k]);
}
int main(){
    rd(loop);
    while(loop--){
        fun();
        int x,y;
        rd2(x,y);
        ///区间的左右端点
        x=first[x];
        y=first[y];
        if(x>y)
            x^=y^=x^=y;
        //cout<<x<<y<<'\12';
        RMQ(x,y);
       // FOR(i,x,y)printf("%4d",occur[i]);
        //FOR(i,1,cnt)printf("%2d ",i);
        //FOR(i,1,cnt)printf("%4d ",occur[i]);
       // FOR(i,1,cnt)printf("%2d ",depth[i]);
        //FOR(i,1,cnt)printf("%2d ",first[i]);
      //  printf("occur %d \t depth %d \t first %d\n",occur[i],depth[i],first[i]);
    }
    return 0;
}
/*
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

*/


第二种做法:

由于树中的每个节点都有通往根的一条路径,因此任一结点对都存在共同的祖先。我们边输入边构造树的双亲表示和多重链表,并通过先跟遍历计算每个节点的层次。为了计算的方便,树的多重链表用vector类储存双亲和层次用数组。

若x与y不同则分析x和y的层次:若x处于较深层次,则取x的父指针为x;否则取y的父指针为y,……,依次类推直到x=y为止。

ACcode:

#pragma warning(disable:4786)//使命名长度不受限制
#pragma comment(linker, "/STACK:102400000,102400000")//手工开栈
#include <map>
#include <set>
#include <queue>
#include <cmath>
#include <stack>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
#define rd(x) scanf("%d",&x)
#define rd2(x,y) scanf("%d%d",&x,&y)
#define rds(x) scanf("%s",x)
#define rdc(x) scanf("%c",&x)
#define ll long long int
#define maxn 100005
#define mod 1000000007
#define INF 0x3f3f3f3f //int 最大值
#define FOR(i,f_start,f_end) for(int i=f_start;i<=f_end;++i)
#define MT(x,i) memset(x,i,sizeof(x))
#define PI  acos(-1.0)
#define E  exp(1)
using namespace std;
vector <int> a[maxn];///多重链表中结点i的链表为一个vector
int f[maxn],r[maxn];///f为父亲指针   r为层次or深度
void DFS(int u,int dep){///从dep层的u结点出发通过先根遍历每个点的深度or层次
    r[u]=dep;
    for(vector<int>::iterator it=a[u].begin();it!=a[u].end();++it)
        DFS(*it,dep+1);
}
int main(){
    int loop,num,n,x,y;
    rd(loop);
    FOR(i,1,loop){
        rd(n);
        FOR(i,0,n-1)a[i].clear();///清空
        MT(f,255);
        FOR(i,0,n-2){
            rd2(x,y);
            a[x-1].push_back(y-1);
            f[y-1]=x-1;
        }
        for(int i=0;;++i)
             if(f[i]<0){DFS(i,0);break;}///从根出发dfs建树
        rd2(x,y);
        x--,y--;
        for(;x!=y;)///若未找到lca则反复计算深层结点的父结点
            if(r[x]>r[y])x=f[x];
            else y=f[y];
        printf("%d\n",x+1);
    }
    return 0;
}
/*
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

*/



你可能感兴趣的:(C++,dp,poj,LCA,RMQ)