zoukankan      html  css  js  c++  java
  • LCA(最近公共祖先)--tarjan离线算法 hdu 2586

    HDU 2586 How far away ?

    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
    Total Submission(s): 11320    Accepted Submission(s): 4119

    Problem Description
     
    There are n houses in the village and some bidirectional roads connecting them. Every day peole always like to ask like this "How far is it if I want to go from house A to house B"? Usually it hard to answer. But luckily int this village the answer is always unique, since the roads are built in the way that there is a unique simple path("simple" means you can't visit a place twice) between every two houses. Yout task is to answer all these curious people.
     
    Input
    First line is a single integer T(T<=10), indicating the number of test cases.
      For each test case,in the first line there are two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses and the number of queries. The following n-1 lines each consisting three numbers i,j,k, separated bu a single space, meaning that there is a road connecting house i and house j,with length k(0<k<=40000).The houses are labeled from 1 to n.
      Next m lines each has distinct integers i and j, you areato answer the distance between house i and house j.
     
    Output
    For each test case,output m lines. Each line represents the answer of the query. Output a bland line after each test case.
     
    Sample Input
    2 3 2 1 2 10 3 1 15 1 2 2 3 2 2 1 2 100 1 2 2 1
     
    Sample Output
    10 25 100 100
     
    Source
    ECJTU 2009 Spring Contest 
    解析:tarjan离线算法求LCA:

    概念描述:

    LCA(Least Common Ancestors):即最近公共祖先,是指这样一个问题:在有根树中,找出某两个结点u和v的所有祖先中距离(u,v)最近的那个公共祖先(也就是离根最远的那个公共祖先)。

     

    时间和空间复杂度:

    • 时间复杂度:当询问次数为Q,节点数为N时,时间复杂度为O(N+Q)。
    • 空间复杂度:①建图时存储的空间大小(树的节点个数个空间);②深搜时遍历树时需要的空间大小(树的最大深度)

     

    算法实现基于基本原理:

    算法是基于DFS并查集来实现的。

     

    算法流程 及 对求LCA(u,v)的证明:

    1. 从根节点(root)开始深搜,
    2. 对于新搜索到的一个节点(x),首先创建由这个结点构成的集合(由par数组维护,par[x]=x,当前这个集合中只有元素x)
    3. 然后依次搜索并处理该节点包含的所有子树(搜素和处理是个递归的过程。结合第5点理解:每搜索完一棵子树,则可确定子树内的LCA询问都已解,其他的LCA询问的结果必然在这个子树之外。)
    4. 当搜索完该节点的所有子树以后,在回溯时,把当前节点的par[x]=(x的父亲节点)(集合归并)
    5. 然后开始处理原先所有询问中包含了该节点的所有询问及求LCA(u,?)(体现了离线算法,对询问次序按深搜时遍历到的节点顺序进行重组
      • 在处理包含该节点的询问中,先判断当前正在处理的这条询问(求LCA(u,v))中另一个节点(v)是否也已经被遍历过
      • 还未遍历,则暂不处理否则LCA(u,v)= FindPar(par[v])(并查集)(证明:因为v是在遍历到u(也就是当前的x节点)之前先遍历到了。如果有一个从当前结点(u)到结点v的询问,且v已被检查过,则由于进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v所在集合的祖先。)
      • 包含该节点的所有询问全部处理完毕

     

    <具体结合遍历和并查集的归并的顺序理解,见图>

    Tarjan离线算法求LCA1

    处理LCA(3,4):因为3在遍历到4之前先被访问到,所以LCA(3,4)=FindPar(par[3])=3;(此时对LCA(4,5)的询问操作暂被跳过。Tarjan离线算法求LCA12

    处理LCA(5,4):因为4在遍历到5之被访问,所以LCA(5,4)=Find(par[4])=2

    扩展:求树上两点(u,v)间的最短距离

    GetDist

         求树上两点间u,v的最短距离的方法:记dis[u]为u到根节点的距离

         那么u到v之间的距离:ans[u][v]=dis[u]+dis[v]-2*dis[lca[u][v]](减去到根节点的公共距离的两倍);

    题解:

     1 #include<cstring>
     2 #include<iostream>
     3 using namespace std;
     4 #include<cstdio>
     5 #define M 201
     6 #include<vector>
     7 #define N 40100
     8 vector <int> que[N];/*储存询问队列*/
     9 int ans[M];/*存着答案*/
    10 int n,m,u,v,k;
    11 struct Edge{
    12     int v,last,w;/*边表*/
    13 }edge[N*2];
    14 int head[N],dis[N]={0};
    15 int T,t=0;
    16 int father[N],ance[N];/*father[N]表示当前的处理的子树,ance代表这个点当前的祖先*/
    17 bool visit[N],root[N];/*visit判断当前的点的子树lca是否求过,root是寻找根节点*/
    18 void add_edge(int u,int v,int w)
    19 {
    20     ++t;
    21     edge[t].v=v;
    22     edge[t].w=w;
    23     edge[t].last=head[u];
    24     head[u]=t;
    25 }
    26 void input()
    27 {
    28     memset(visit,false,sizeof(visit));
    29     memset(root,false,sizeof(root));
    30     memset(head,0,sizeof(head));
    31     memset(dis,0,sizeof(dis));
    32     memset(edge,0,sizeof(edge));
    33     scanf("%d%d",&n,&m);
    34     for(int i=1;i<=n-1;++i)
    35     {
    36         scanf("%d%d%d",&u,&v,&k);
    37         add_edge(u,v,k);
    38         root[v]=true;
    39         ance[i]=i;/*初始化*/
    40         father[i]=i;
    41     }
    42     for(int i=1;i<=m;++i)
    43     {
    44         scanf("%d%d",&u,&v);
    45         que[u].push_back(i);/*因为离线算法求出结果是无法知道他的查询顺序的,但是我们要按照查询顺序输出,所以就在查询序列的偶数位存着下一位的在ans的顺序*/
    46         que[u].push_back(v);
    47         que[v].push_back(i);
    48         que[v].push_back(u);
    49     }
    50 }
    51 int find(int x)
    52 {
    53     return (father[x]==x)?father[x]:father[x]=find(father[x]);
    54 }
    55 void tarjan(int k,int w)
    56 {
    57     ance[k]=k;
    58     dis[k]=w;
    59     for(int l=head[k];l;l=edge[l].last)
    60     {
    61         tarjan(edge[l].v,dis[k]+edge[l].w);
    62         father[edge[l].v]=k;/*father存着当前点与全部的子树上的集合,同时把子树直接点的祖先设为k,所以查询一个子树上的点的区间的时候,要先用find找出代表元素,再求祖先*/
    63         ance[edge[l].v]=k;
    64     }
    65     visit[k]=true;
    66     int size=que[k].size();
    67     for(int i=1;i<size;i+=2)
    68     {
    69         if(visit[que[k][i]])/*如果这条边的另一个点的lca已经求出来,那么这个点所在集合的祖先就是这两个点的最近公共祖先*/
    70         {
    71             int zu=ance[find(que[k][i])];
    72             ans[que[k][i-1]]=dis[k]+dis[que[k][i]]-2*dis[zu];
    73         }
    74     }
    75 }
    76 int main()
    77 {
    78     scanf("%d",&T);
    79     while(T--)
    80     {
    81         input();
    82         for(int i=1;i<=n;++i)
    83         {
    84             if(!root[i])/*从根节点开始深搜*/
    85             {
    86                 tarjan(i,0);
    87                 break;
    88             }
    89         }
    90         for(int i=1;i<=m;++i)
    91           printf("%d
    ",ans[i]);
    92     }
    93     return 0;
    94 }

     

  • 相关阅读:
    几种负载均衡技术的实现
    gevent和tornado异步
    Android笔记:invalidate()和postInvalidate() 的区别及使用——刷新ui
    ubuntu终端颜色配置
    应用程序基础及组件(续)
    安卓架构
    Linux下安卓ndk混合编译调用so方法——QuickStart学习
    JAVA反射机制
    BroadcastReceiver应用详解——广播
    库会因为权限问题无法打开——selinux开启严格模式
  • 原文地址:https://www.cnblogs.com/c1299401227/p/5499793.html
Copyright © 2011-2022 走看看