zoukankan      html  css  js  c++  java
  • LCA(倍增在线算法) codevs 2370 小机房的树

    codevs 2370 小机房的树

      时间限制: 1 s

     空间限制: 256000 KB
     题目等级 : 钻石 Diamond
    题目描述 Description

    小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上。有一天,他们想爬到一个节点上去搞基,但是作为两只虫子,他们不想花费太多精力。已知从某个节点爬到其父亲节点要花费 c 的能量(从父亲节点爬到此节点也相同),他们想找出一条花费精力最短的路,以使得搞基的时候精力旺盛,他们找到你要你设计一个程序来找到这条路,要求你告诉他们最少需要花费多少精力

    输入描述 Input Description
    第一行一个n,接下来n-1行每一行有三个整数u,v, c 。表示节点 u 爬到节点 v 需要花费 c 的精力。
    第n+1行有一个整数m表示有m次询问。接下来m行每一行有两个整数 u ,v 表示两只虫子所在的节点
    输出描述 Output Description

    一共有m行,每一行一个整数,表示对于该次询问所得出的最短距离。

    样例输入 Sample Input

    3

    1 0 1

    2 0 1

    3

    1 0

    2 0

    1 2

    样例输出 Sample Output

    1

    1

    2

    数据范围及提示 Data Size & Hint

    1<=n<=50000, 1<=m<=75000, 0<=c<=1000

    分类标签 Tags 点此展开 

    最近公共祖先 图论
    资料来自:http://www.tuicool.com/articles/N7jQV32
                  http://www.cnblogs.com/wuminye/p/3532397.html

    最近公共祖先 LCA 倍增法

    【简介】

          解决LCA问题的倍增法是一种基于倍增思想的在线算法。

    【原理】

         原理和同样是使用倍增思想的RMQ-ST 算法类似,比较简单,想清楚后很容易实现。

         对于每个节点u , ancestors[u][k] 表示 u 的第2k个祖先是谁。很容易就想到递推式: ancestors[j][i] = ancestors[ancestors[j][i - 1]][i - 1];  根据二进制原理,理论上 u 的所有祖先都可以根据ancestors数组多次跳转得到,这样就间接地记录了每个节点的祖先信息。
         查询LCA(u,v)的时候:
             (一)u和v所在的树的层数如果一样,令u'=u。否则需要平衡操作(假设u更深),先找到u的一个祖先u', 使得u'的层数和v一样,此时LCA(u,v)=LCA(u',v) 。证明很简单:如果LCA(u,v)=v , 那么u'一定等于v ;如果LCA(u,v)=k ,k!=v ,那么k 的深度一定小于 v , u、u'、v 一定在k的子树中;综上所述,LCA(u,v)=LCA(u',v)一定成立。

             (二)此时u' 和 v 的祖先序列中一开始的部分一定有所重叠,重叠部分的最后一个元素(也就是深度最深,与u'、v最近的元素)就是所求的LCA(u,v)。这里ancestors数组就可以派上用场了。找到第一个不重叠的节点k,LCA(u,v)=ancestors[k][0] 。 找k的过程利用二进制贪心思想,先尽可能跳到最上层的祖先,如果两祖先相等,说明完全可以跳小点,跳的距离除2,这样一步步跳下去一定可以找到k。

        

    1. DFS预处理出所有节点的深度和父节点
    
    inline void dfs(int u)
    {
      int i;
      for(i=head[u];i!=-1;i=next[i])  
      {  
        if (!deep[to[i]])
        {            
          deep[to[i]] = deep[u]+1;
          p[to[i]][0] = u; //p[x][0]保存x的父节点为u;
          dfs(to[i]);
        }
      }
    }
    2. 初始各个点的2^j祖先是谁 ,其中 2^j (j =0...log(该点深度))倍祖先,1倍祖先就是父亲,2倍祖先是父亲的父亲......。
    
    void init()
    {
      int i,j;
      //p[i][j]表示i结点的第2^j祖先
      for(j=1;(1<<j)<=n;j++)
        for(i=1;i<=n;i++)
          if(p[i][j-1]!=-1)
            p[i][j]=p[p[i][j-1]][j-1];//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先
    }
    3.从深度大的节点上升至深度小的节点同层,如果此时两节点相同直接返回此节点,即lca。
    
    否则,利用倍增法找到最小深度的 p[a][j]!=p[b][j],此时他们的父亲p[a][0]即lca。
    
    int lca(int a,int b)//最近公共祖先
    {
      int i,j;
      if(deep[a]<deep[b])swap(a,b);
      for(i=0;(1<<i)<=deep[a];i++);
      i--;
      //使a,b两点的深度相同
      for(j=i;j>=0;j--)
        if(deep[a]-(1<<j)>=deep[b])
          a=p[a][j];
      if(a==b)return a;
      //倍增法,每次向上进深度2^j,找到最近公共祖先的子结点
      for(j=i;j>=0;j--)
      {
        if(p[a][j]!=-1&&p[a][j]!=p[b][j])
        {
          a=p[a][j];
          b=p[b][j];
        }
      }
      return p[a][0];
    }

    附上题解:

     1 #define N 50100
     2 #include<iostream>
     3 using namespace std;
     4 #include<cstdio>
     5 #include<cstring>
     6 #define L 17
     7 struct Edge{
     8     int v,last,c;
     9 }edge[N*6]; 
    10 int head[N],p[N][L];
    11 int deep[N]={0};
    12 int root[N]={0};
    13 long long dis[N]={0};
    14 int n,m,u,v,c,t=0;
    15 void add_edge(int u,int v,int w)
    16 {
    17     ++t;
    18     edge[t].v=v;/*建边*/
    19     edge[t].c=w;
    20     edge[t].last=head[u];
    21     head[u]=t;
    22     //root[u]++;
    23 }
    24 void input()
    25 {
    26     scanf("%d",&n);
    27     for(int i=1;i<n;++i)
    28     {
    29         scanf("%d%d%d",&u,&v,&c);
    30         add_edge(u,v,c);
    31         add_edge(v,u,c);
    32     }
    33     memset(p,-1,sizeof(p));/*因为节点编号是从0开始的,所以把祖先不存在,设为-1*/
    34 }
    35 void dfs(int u,long long di)
    36 {
    37     dis[u]=di;/*统计u到根节点的距离*/
    38     for(int l=head[u];l;l=edge[l].last)
    39     {
    40         if(!deep[edge[l].v])
    41         {
    42             deep[edge[l].v]=deep[u]+1;/*处理孩子的深度*/
    43             p[edge[l].v][0]=u;/*初始化p数组*/
    44             dfs(edge[l].v,di+edge[l].c);
    45         }
    46     }
    47 }
    48 void init()
    49 {
    50     int i,j;
    51     for(j=1;(1<<j)<n;j++)
    52       for(int i=0;i<n;++i)
    53           if(p[i][j]=-1)
    54           p[i][j]=p[p[i][j-1]][j-1];/*DP处理出i的所有2^j祖先是谁*/
    55 }
    56 int lca(int a,int b)/*求最近公共祖先*/
    57 {
    58     int i,j;
    59     if(deep[a]<deep[b]) swap(a,b);
    60     for(i=0;(1<<i)<=deep[a];++i);
    61         i--;/*i为估计a到根节点的最远距离,下边的平衡操作,跳点从i开始,一定可以实现*/
    62     for(j=i;j>=0;--j)
    63       if(deep[a]-deep[b]>=(1<<j))/*倍增缩短a与b之间的距离*/
    64         a=p[a][j];
    65     if(a==b) return a;/*当a和b到了同一深度的时候,判断是否已经相同了*/
    66     for(int j=i;j>=0;--j)
    67     {
    68         if(p[a][j]!=-1&&p[a][j]!=p[b][j])
    69         {
    70             a=p[a][j];/*最终的a是lca的子节点*/
    71             b=p[b][j];
    72         }
    73        
    74     }/*先大步大步的蹦,每蹦一步,路程减少,下次蹦前一次的一半,直到蹦不了了,就是答案*/
    75     return p[a][0];   
    76 }
    77 /*当a有祖先,并且a,b的祖先不相同的时候,(我们想要寻找的就是lca的子节点,也就是最小深度的p[a][j]!=p[b][j]),根据二进制原理,一定可以通过各种组合走到每一个祖先*/
    78 int main()
    79 {
    80     input();
    81     dfs(0,0);/*题目意思是0为根节点*/
    82     /*for(int i=0;i<n;++i)
    83     {
    84         if(root[i]==2)
    85         {
    86             dfs(i,0);/*如果是一棵二叉树,可以统计出度为2的点是根节点*/
    87             break;
    88         }
    89     }*/
    90     init();
    91     scanf("%d",&m);
    92     while(m--)
    93     {
    94         scanf("%d%d",&u,&v);
    95         int zu=lca(u,v);/*在线算法,可以按照顺序查询*/
    96         cout<<dis[u]+dis[v]-2*dis[zu]<<endl;/*求最近距离的公式*/
    97     }
    98     return 0;
    99 }
  • 相关阅读:
    路由懒加载
    Generator函数
    属性特性get/set
    审核功能
    纯js实现最简单的文件上传(后台使用MultipartFile)
    shiro+redis多次调用doReadSession方法的解决方案
    nginx-windows版
    Spring—Quartz定时调度CronTrigger时间配置格式说明与实例
    java中获取文件目录
    mysql 在update中实现子查询的方式
  • 原文地址:https://www.cnblogs.com/c1299401227/p/5503540.html
Copyright © 2011-2022 走看看