图论----最近公共祖先问题(LCA:Least Common Ancestors问题)

 

多年后重新回来总结一下倍增LCA的初始化,倍增数组等

  首先我们需要初始化的是deep数组

  memset(deep,0x3f,sizeof(deep))

  初始化这个数组的目的是为了可以不用st[]数组,来判断是否看过某些点

  

  这里设倍增数组为h[N][25]

  初始化 deep[root]=0,h[root][0]=0 ,deep[0]=-1;

  这里这样初始化的目的,如图:

     

    可以让超过root点的h[i][j]一直等于0

   含义即是i点跳2^j步后超过root点 所到达的点一直都是0

   然后设置deep[0]=-1,只要是为了下面LCA()函数中

 这个判断中让b跳出了root点到0点不会被接受

 

 

 

 

 

 

 注意到:在一棵树上的任意两个点之间一定都有公共祖先(最坏的情况下就是根节点了)

求两个点之间的最短距离其实就是求: 两个点到最近公共祖先的距离之和

那如何求最近公共祖先是谁呢?

  1.首先我们定义一个函数lca(int a,int b),其作用是返回a,b两点的最近公共祖先

  2.定义一个depth[]数组,代表每一个点在树中的深度; 规范  depth[root]=1 

   定义一个f[N][logN+1]数组(N是节点总数),f[i][j]代表在点i处往上走2^j个点到达的点是f[i][j];

     规定 f[i][0]=i的父节点,跳出树的节点或根本不在树上的节点depth[]=0

 

 

 lca函数具体做法:

  首先,同过depth[],将两个点a,b跳到同一层(这里我们规定depth[a]<=depth[b],所以只要对a点进行操作即可)

      如何跳呢?

      因为我们有f[i][j]; 首先将a点跳到与b点相同深度我们并不知道要跳过几个点(假设要跳x个点),

 但是我们知道在a点跳2^0个点到达的深度是:depth[f[a][0]],其他以此类推

一个数,一定能够由2的倍数拼凑出来

那么x也一定能由2的倍数拼凑出来,我们可以枚举for (int i=logN;i>=0;i--) 表示跳2^i个点(i<=logN的原因是跳2^logN个点会超过树上的全部点)

if  depth[f[a][i]]<=depth[b]; 那么:a=f[a][i];

  到了同一个深度后,将两个点一起跳枚举for (int i=logN;i>=0;i--) ,一直跳到最近公共祖先的下一个点

  注意:如果两个点在同一层时已经是同一个点了,则直接返回,这时b点就是最近公共祖先

  如何判断是跳到了最近公共祖先的下一个点?

即两个点是不同时,说明还未到最近公共祖先及上面的点

然后返回f[a][0]即为最近公共祖先

 

 

   如何求depth与f[i][j]?

  f[i][j]=f【f【i】【j-1】】【j-1】;

 

 

 

  1 #include <iostream>
  2 #include <algorithm>
  3 #include <cstring>
  4 // bfs时用的;
  5 #include <queue>
  6 //建树用的;
  7 #include <vector>
  8 using namespace std;
  9 typedef pair<int, int> PII;
 10 const int N = 10010;
 11 // fa[i][j]表示从点i开始出发跳2^j步能够到达的点为fa[i][j];
 12 //由于0<logN<16(logN是以二为底的log),即跳2^16步会超过所以点数,
 13 //最大跳2^15步,则j是从0~15,开数组开16位;
 14 //哨兵:对于超过了树的点数的点为0;
 15 int fa[N][14];
 16 //记录一下每个点的深度,root的深度是1,哨兵(不存在的点,即跳出树的点)的深度为0,
 17 int depth[N];
 18 //记录每一个点到顶点的距离
 19 int dist[N];
 20 vector<PII> tree[N];
 21 // bfs预处理fa,depth两个数组;
 22 //预处理时间复杂度为O(nlogn);
 23 //遍历到每一个点,对于每一个点循环logN次求出每一个点的fa[i][j]来
 24 void bfs(int root)
 25 { //宽搜不容易因为递归层数过多爆栈
 26     memset(depth, 0x3f, sizeof(depth));
 27     memset(dist, 0, sizeof(dist));
 28     queue<int> q;
 29     depth[0] = 0, depth[root] = 1;
 30     q.push(root);
 31     while (q.size())
 32     {
 33         int t = q.front();
 34         q.pop();
 35         for (int i = 0; i < tree[t].size(); i++)
 36         {
 37             int child = tree[t][i].first;
 38             //注意:因为这里是有向树,则一定要除去已经看过的点
 39             if (depth[child] > depth[t] + 1)
 40             {
 41                 q.push(child);
 42                 //处理每一个点到顶点的距离:
 43                 dist[child] = dist[t] + tree[t][i].second;
 44                 //处理深度
 45                 depth[child] = depth[t] + 1;
 46                 //处理fa数组
 47                 fa[child][0] = t;
 48                 //这里15就是通过上面logN算出来的;
 49                 // 0步上面已经处理过了
 50                 for (int j = 1; j <= 13; j++)
 51                     fa[child][j] = fa[fa[child][j - 1]][j - 1];
 52             }
 53         }
 54     }
 55 }
 56 //一次询问的时间复杂度是O(logN);
 57 int lca(int a, int b)
 58 {
 59     if (depth[a] < depth[b])
 60         swap(a, b);
 61     // 1.首先让a点与b点处于同一层
 62     for (int k = 13; k >= 0; k--)
 63         if (depth[fa[a][k]] >= depth[b])
 64         {
 65             a = fa[a][k];
 66         }
 67     //让其同一层后:
 68     // 1.如果已经是同一个点了:
 69     if (a == b)
 70         return b;
 71     // 2.还不是同一个点,让a,b两点一起向上跳,直到到了最近公共祖先的下一层:
 72     //如何判断是最近公共祖先的下一层?
 73     //首先,两点肯定不相同:fa[a][k]!=fa[b][k];
 74     //其次在上面的情况下:fa[a][0]==fa[b][0],再移动一点就相同说明到达了最近公共祖先
 75     for (int k = 13; k >= 0; k--)
 76     {
 77         if (fa[a][k] != fa[b][k])
 78         {
 79             a = fa[a][k];
 80             b = fa[b][k];
 81         }
 82     }
 83     //因为树上任意两个点一定有公共祖先
 84     //经过上面的处理,一定两点到达了最近公共祖先的下一层;
 85     //返回两点的最近公共祖先;
 86     return fa[a][0];
 87 }
 88 int main()
 89 {
 90     //默认root=1;
 91     int n, m, rd[N], root = 1;
 92     cin >> n >> m;
 93     for (int i = 1; i <= n - 1; i++)
 94     {
 95         int a, b, w;
 96         scanf("%d%d%d", &a, &b, &w);
 97         tree[a].push_back({b, w});
 98         tree[b].push_back({a, w});
 99     }
100     bfs(root);
101     //开始处理读入:
102     while (m--)
103     {
104         int a, b;
105         scanf("%d%d", &a, &b);
106         //树上任意两个点一定有公共祖先
107         //函数lca的作用是返回点a,b的最近公共祖先;
108         int node = lca(a, b);
109         cout << dist[a] + dist[b] - dist[node] * 2 << endl;
110     }
111     return 0;
112 }

 《树剖求lca》

这里有几个数组是必要的,首先我们是通过top[]来判断是否在同一条链上

而要知道top[],就要知道son[],要知道son[]就要知道sizer[]

像f[],dep[]数组更是必要的

更详细的树剖内容:https://www.cnblogs.com/cilinmengye/p/16607480.html

 1 int sizer[N], f[N], dep[N], son[N];
 2 void dfs1(int x, int from, int d)
 3 {
 4     f[x] = from, dep[x] = d, sizer[x] = 1, son[x] = -1;
 5     for (int i = 0; i < tree[x].size(); i++)
 6     {
 7         int child = tree[x][i];
 8         if (child == from)
 9             continue;
10         dfs1(child, x, d + 1);
11         sizer[x] += sizer[child];
12         if (son[x] == -1 || sizer[son[x]] < sizer[child])
13             son[x] = child;
14     }
15 }
16 // sum[i]表示点i到根节点的权值
17 int top[N];
18 void dfs2(int x, int from, int t)
19 {
20     top[x] = t;
21     if (son[x] != -1)
22         dfs2(son[x], x, t);
23     for (int i = 0; i < tree[x].size(); i++)
24     {
25         int child = tree[x][i];
26         if (child == from || child == son[x])
27             continue;
28         dfs2(child, x, child);
29     }
30 }
31 int lca(int a, int b)
32 {
33     while (top[a] != top[b])
34     {
35         if (dep[top[a]] < dep[top[b]])
36             swap(a, b);
37         a = f[top[a]];
38     }
39     if (dep[a] > dep[b])
40         swap(a, b);
41     return a;
42 }

 

posted @ 2022-08-08 23:24  次林梦叶  阅读(71)  评论(0)    收藏  举报