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 }
  • 相关阅读:
    hdu 1455 N个短木棒 拼成长度相等的几根长木棒 (DFS)
    hdu 1181 以b开头m结尾的咒语 (DFS)
    hdu 1258 从n个数中找和为t的组合 (DFS)
    hdu 4707 仓鼠 记录深度 (BFS)
    LightOJ 1140 How Many Zeroes? (数位DP)
    HDU 3709 Balanced Number (数位DP)
    HDU 3652 B-number (数位DP)
    HDU 5900 QSC and Master (区间DP)
    HDU 5901 Count primes (模板题)
    CodeForces 712C Memory and De-Evolution (贪心+暴力)
  • 原文地址:https://www.cnblogs.com/c1299401227/p/5503540.html
Copyright © 2011-2022 走看看