日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

LCA 最近公共祖先(RMQ、树上倍增、Tarjan),树上两点距离,线段重合长度

發布時間:2024/4/18 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 LCA 最近公共祖先(RMQ、树上倍增、Tarjan),树上两点距离,线段重合长度 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

對于LCA的一些理解

RMQ
  • dfs處理樹
    對于一個樹形結構,可以用dfs將一顆樹轉化成數組,數組中記錄每個點的標號,這樣數組就按照dfs的順序把樹存了下來

  • 確定祖先的范圍
    對于詢問的節點X和Y, X、Y的祖先一定存在于數組中X、Y第一次出現的區間內,而且祖先就是區間內深度最小的節點

  • RMQ
    dfs的復雜度就是樹邊數的二倍,所以區間查詢需要優化,這里可以用RMQ算法,預處理結果O(1)得到最值

void dfs(int pre, int x, int step) {depth[x] = step;first[x] = cnt;tree[cnt] = x;cnt++;int len = g[x].size();for (int i = 0; i < len; ++i) {if (g[x][i] == pre) continue;dfs(x, g[x][i], step + 1);tree[cnt++] = x;}return; } int Min(int a, int b) {if (depth[a] < depth[b]) return a;else return b; } void RMQ() {for (int i = 1; i <= cnt; ++i) {dp[i][0] = tree[i];}for (int i = 1; (1 << i) <= cnt; ++i) {for (int j = 1; j + (1 << i) - 1 <= cnt; ++j) {dp[j][i] = Min(dp[j][i - 1], dp[j + (1 << (i - 1))][i - 1]);}} } int LCA(int a, int b) {int l = first[a];int r = first[b];if (l > r) swap(l, r);int len = log2(r - l + 1);return Min(dp[l][len], dp[r - (1 << len) + 1][len]); }

樹上倍增

father[ i ] [ j ]表示 i 的第 2 ^ j 個父親

  • dfs
    dfs在處理深度的同時更新每個人的father數組
void dfs(int pre, int x) {// 更新fatherfor (int i = 1; i <= log2(n); ++i) {if (fa[x][i - 1] == 0) break;fa[x][i] = fa[fa[x][i - 1]][i - 1];}// 記錄深度depth[x] = depth[pre] + 1;int len = g[x].size();for (int i = 0; i < len; ++i) {int son = g[x][i];if (son == pre) continue;fa[son][0] = x;dfs(x, son);} }
  • LCA
    首先將兩個節點的深度調成一樣,如果深度相同而且是同一個點就找到最近的祖先了,如果深度相同但是節點不同,這時就要向上倍增,需要注意的是倍增的中止條件不是兩個節點相同,而是節點的父親相同。因為每次倍增的范圍很大,很可能超過最近公共祖先,我們可以從最大的祖先開始,如果祖先相同縮小范圍,如果祖先不同更新兩個點的狀態繼續找,最后兩個人的最近公共祖先一定是father[ i ][ 0 ]
int LCA(int a, int b) {if (depth[a] < depth[b]) swap(a, b);int depth_x = depth[a] - depth[b];// 先將兩個點的深度調成一樣for (int i = 0; i <= log2(depth_x); ++i) {if (depth_x & (1 << i))a = fa[a][i];}// 如果深度一樣,而且相同直接返回if (a == b) return a;// 從最遠的父親開始for (int i = log2(depth[a]); i >= 0; --i) {// 如果父親不同就向上更新if (fa[a][i] != fa[b][i]) {a = fa[a][i];b = fa[b][i];}}return fa[a][0]; }

Tarjan

完美結合并查集dfs 在**O(n + q)**解決LCA問題。
dfs的時候需要做以下處理:

  • 對于當前點x來說:如果存在詢問(x,y),而且y已經dfs訪問過,那么LCA就是 find(y)
  • 回溯的時候要將son的祖先設置為x(子樹的祖先始終為根節點
int n; LL dp[maxn], ans[maxn]; int pre[maxn], vis[maxn]; struct ac{int v, d; }; vector<ac> G[maxn], Q[maxn];int find(int x) {return x == pre[x] ? x : pre[x] = find(pre[x]); }void Tarjan(int fa, int x) {vis[x] = 1;// 更新詢問for (auto it : Q[x]) {if (!vis[it.v]) continue;ans[it.d] = dp[x] + dp[it.v] - 2 * dp[find(it.v)];}for (auto it : G[x]) {if (it.v == fa) continue;dp[it.v] = dp[x] + it.d;Tarjan(x, it.v);pre[it.v] = x; // 更新祖先}// for (int i = 0; i < (int)Q[x].size(); ++i) {// if (!vis[ Q[x][i].v ]) continue;// ans[ Q[x][i].d ] = dp[x] + dp[ Q[x][i].v ] - 2 * dp[find( Q[x][i].v )];// }// for (int i = 0; i < (int)G[x].size(); ++i) {// int son = G[x][i].v;// if (son == fa) continue;// dp[ G[x][i].v ] = dp[x] + G[x][i].d;// Tarjan(x, son);// pre[ G[x][i].v ] = x; // } }
求樹上兩點的距離

樹上兩點的距離是唯一的,距離 = depth[X] + depth[Y] - 2 * depth[ancestor]

線段重合的長度

思想和求距離一樣,例如A到C和B到C的重合長度 = (dis_ac + dis_bc - dis_ab)/ 2

int dis(int a, int b) {int ancestor = LCA(a, b);int depth_a = depth[a];int depth_b = depth[b];int depth_ancestor = depth[ancestor];return depth_a + depth_b - 2 * depth_ancestor; }

總結

以上是生活随笔為你收集整理的LCA 最近公共祖先(RMQ、树上倍增、Tarjan),树上两点距离,线段重合长度的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。