LCA(倍增在线算法) codevs 2370 小机房的树

Posted tech-chen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LCA(倍增在线算法) codevs 2370 小机房的树相关的知识,希望对你有一定的参考价值。

【简介】

      解决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 }

 

以上是关于LCA(倍增在线算法) codevs 2370 小机房的树的主要内容,如果未能解决你的问题,请参考以下文章

LCA 算法倍增

CodeVS 1036 商务旅行

倍增法求LCA

图论-最近公共祖先-在线树上倍增

倍增法求LCA——在线

SPOJ QTREE2 (LCA - 倍增 在线)