暑假集训.11(最近公共祖先)

并查集  是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题

并查集的基本操作有两个:

  merge(x, y):把元素x 和元素y 所在的集合合并,要求x 和y 所在的集合不相交,如果相交则不合并。

  find(x):找到元素x 所在的集合的代表,该操作也可以用于判断两个元素是否位于同一个集合,只要将它们各自的代表比较一下就可以了。

求最近公共祖先的基本思路;

               1.任选一个点为根节点,从根节点开始。

      2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。

      3.若是v还有子节点,返回,否则下一步。

      4.合并v到u上。

      5.寻找与当前点u有询问关系的点v。

      6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。

模板题    Nearest Common Ancestors

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

 
  1. 2

  2. 16

  3. 1 14

  4. 8 5

  5. 10 16

  6. 5 9

  7. 4 6

  8. 8 4

  9. 4 10

  10. 1 13

  11. 6 15

  12. 10 11

  13. 6 7

  14. 10 2

  15. 16 3

  16. 8 1

  17. 16 12

  18. 16 7

  19. 5

  20. 2 3

  21. 3 4

  22. 3 1

  23. 1 5

  24. 3 5

Sample Output

 
  1. 4

  2. 3

链式前向星写法


#include<iostream>

#include<cstdio>

#include<cstring>

#include<cmath>

#include<vector>

#include<queue>

#define eps 1e-8

#define memset(a,v) memset(a,v,sizeof(a))

using namespace std;

typedef long long int LL;

const int MAXL(1e6);

const int INF(0x7f7f7f7f);

const int mod(1e9+7);

int dir[4][2]= {{-1,0},{1,0},{0,1},{0,-1}};

struct node

{

    int to;

    int next;

}edge[MAXL+50];

int head[MAXL+50];

int father[MAXL+50];

bool vis[MAXL+50];

bool is_root[MAXL+50];

int n;

int cnt;

int cx,cy;

int ans;

int root;

 

 

int Find(int x)

{

    if(x!=father[x])

        father[x]=Find(father[x]);

    return father[x];

}

 

void Join(int x,int y)

{

    int fx=Find(x),fy=Find(y);

    if(fx!=fy)

        father[fy]=fx;

}

 

void add_edge(int x,int y)

{

    edge[cnt].to=y;

    edge[cnt].next=head[x];

    head[x]=cnt++;

}

 

void init()

{

    cnt=0;

    memset(head,-1);

    memset(vis,false);

    memset(is_root,true);

    scanf("%d",&n);

    for(int i=0;i<=n;i++)

        father[i]=i;

    for(int i=1;i<n;i++)

    {

        int x,y;

        scanf("%d%d",&x,&y);

        add_edge(x,y);

        is_root[y]=false;

    }

    for(int i=1;i<=n;i++)

        if(is_root[i]==true)

            root=i;

}

 

void LCA(int u)

{

    for(int i=head[u];~i;i=edge[i].next)

    {

        int v=edge[i].to;

        LCA(v);

        Join(u,v);

        vis[v]=true;

 

    }

    if(cx==u&&vis[cy]==true)

        ans=Find(cy);

    if(cy==u&&vis[cx]==true)

        ans=Find(cx);

}

void solve()

{

    scanf("%d%d",&cx,&cy);

    LCA(root);

}

int main()

{

    int T;

    scanf("%d",&T);

    while(T--)

    {

        init();

        solve();

        cout<<ans<<endl;

    }

}

vector模拟邻接表写法


#include<iostream>

#include<cstdio>

#include<cstring>

#include<cmath>

#include<vector>

#include<queue>

#define eps 1e-8

#define memset(a,v) memset(a,v,sizeof(a))

using namespace std;

typedef long long int LL;

const int MAXL(1e4);

const int INF(0x7f7f7f7f);

const int mod(1e9+7);

int dir[4][2]= {{-1,0},{1,0},{0,1},{0,-1}};

int father[MAXL+50];

bool is_root[MAXL+50];  //记录该店是不是根结点

bool vis[MAXL+50];      //标记该点是否被访问过

vector<int>v[MAXL+50];  //存图

int root;    //为找到的根结点

int cx,cy;   //要查询的两点

int ans;

int Find(int x)

{

    if(x!=father[x])

        father[x]=Find(father[x]);

    return father[x];

}

 

void Join(int x,int y)

{

    int fx=Find(x),fy=Find(y);

    if(fx!=fy)

        father[fy]=fx;

}

 

void LCA(int u)

{

    for(int i=0; i<v[u].size(); i++)  //对根结点的所有子结点遍历

    {

        int child=v[u][i];

        if(!vis[child])

        {

            LCA(child);     //递归查找每一个结点,直到到叶子结点为止

            Join(u,child);  //回溯的时候更新father用的

            //例如把题目中的[9]更新为5

            vis[child]=true;  //访问过的点vis数组为true

        }

    }

    if(u==cx&&vis[cy]==true) //若和u有关系的点被访问过,

        ans=Find(cy);    //则最近公共祖先为那个有关系点的祖先

    if(u==cy&&vis[cx]==true)  //若没有被访问过,则不操作

        ans=Find(cx);

 

}

 

void init()

{

    memset(is_root,true);

    memset(vis,false);

    int n;

    scanf("%d",&n);

    for(int i=0; i<=n; i++)

        v[i].clear();

    for(int i=1; i<=n; i++)

        father[i]=i;

    for(int i=1; i<n; i++)

    {

        int x,y;

        scanf("%d%d",&x,&y);

        v[x].push_back(y);

        is_root[y]=false;   //y点有入度,所以y点不是根结点

    }

    scanf("%d%d",&cx,&cy);  //cx,cy为要查询的两点

    for(int i=1; i<=n; i++) //找根结点

    {

        if(is_root[i]==true)

        {

            root=i;

            break;

        }

    }

 

}

int main()

{

    int T;

    scanf("%d",&T);

    while(T--)

    {

        init();      //建树(图),找根结点

        LCA(root);   

        cout<<ans<<endl;

    }

}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值