<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      求解 LCA 四種方法及其比較

      本文原作于 2025 年 10 月 24 日,關(guān)于 Tarjan 算法的部分補充于 2025 年 11 月 4 日。

      昨天看到歲歲似今朝以“學(xué)不成名誓不還”的勇氣學(xué) LCA,并感嘆“LCA 是我最嚴(yán)厲的母親”,心血來潮,也學(xué)了一下。翻看洛谷玲瑯滿目的題解,竟學(xué)會了種方法(最后一種于 2025 年 11 月 4 日補充),在此總結(jié)并進行比較,希望對大家和自己有所幫助。

      倍增求 LCA

      大家可能知道使用“暴力跳躍”來求 LCA 的方法:我們循環(huán)找深度較深的點的父節(jié)點,直到兩個節(jié)點深度相同,然后一起向上跳。倍增法基本也是這個思路,只是通過倍增優(yōu)化掉了逐步向上跳的過程。

      我們設(shè) \(f_{u, j}\) 為節(jié)點 \(u\) 以上的第 \(2^j\) 個節(jié)點。顯然 \(f_{i, 0}\) 表示節(jié)點 \(i\) 的父節(jié)點,可以通過 DFS 求得所有 \(f_{u, 0}\)。想要求解每一個 \(i\) 節(jié)點跳 \(2^j\) 到的節(jié)點,等同于 \(i\) 節(jié)點先往上跳 \(2^{j-1}\) 步到的節(jié)點,再往上跳 \(2^{j-1}\) 步到的最終節(jié)點。因此可以得到狀態(tài)轉(zhuǎn)移方程:

      \[f_{i, j} = f_{f_{i, j-1}, j-1} \]

      求解 LCA 時,需要找到兩個點上面同一深度的點,如果兩個節(jié)點相同,則返回兩者之間任一節(jié)點,否則一起向上跳相同步數(shù)直至求出 LCA 為止。

      模板題(洛谷 P3379)參考代碼:

      #include <bits/stdc++.h>
      typedef long long ll;
      const int N = 1e6+10;
      int f[N][30], dep[N], n, m, s;
      std::vector<int> g[N];
      
      void dfs(int u, int par) {
        f[u][0] = par;
        dep[u] = dep[par] + 1;
        for (auto &v: g[u]) {
          if (v != par) dfs(v, u);
        }
      }
      
      void init() {
        for (int j = 1; (1 << j) <= n; j++) {
          for (int i = 1; i <= n; i++) {
            f[i][j] = f[f[i][j-1]][j-1];
          }
        }
      }
      
      int lca(int u, int v) {
        if (dep[u] < dep[v]) std::swap(u, v);
        for (int i = 22; i >= 0; i--) {
          if (dep[f[u][i]] >= dep[v]) u = f[u][i];
        }
        if (u == v) return u;
        for (int i = 22; i >= 0; i--) {
          if (f[u][i] != f[v][i]) {
            u = f[u][i];
            v = f[v][i];
          }
        }
        return f[u][0];
      }
      
      int main() {
        std::ios::sync_with_stdio(false); std::cin.tie(0);
        std::cin >> n >> m >> s;
        for (int i = 1; i <= n-1; i++) {
          int a, b;
          std::cin >> a >> b;
          g[a].push_back(b);
          g[b].push_back(a);
        }
        dfs(s, 0);
        init();
        for (int i = 1; i <= m; i++) {
          int a, b;
          std::cin >> a >> b;
          std::cout << lca(a, b) << '\n';
        }
        return 0;
      }
      

      DFS 序求 LCA

      DFS 序是指對一棵樹進行深度優(yōu)先搜索得到的序列。DFN 是指樹上每個節(jié)點在 DFS 序中出現(xiàn)的位置。

      設(shè)樹上兩個節(jié)點 \(u\)\(v\) 的 LCA 為 \(d\),且 \(u \neq v\)。不妨設(shè) \(\mathrm{dfn}(u) < \mathrm{dfn}(v)\),顯然 \(v\) 不是 \(u\) 的祖先。分類討論:

      1. \(u\) 不是 \(v\) 的祖先。 那么容易證明,DFS 序在 \(u\)\(v\) 之間的節(jié)點均為 \(d\) 的后代。因此,我們可以找到滿足 \(\mathrm{dfn}(u) < v' < \mathrm{dfn}(v)\) 且深度最小的節(jié)點 \(v'\) ,那么 \(v'\) 的父節(jié)點就是 \(d\)

      2. \(u\)\(v\) 的祖先。 如果還按照剛才的方法來求,可能會求得 \(u\) 的祖先,這不是我們想要的結(jié)果。但是如果把查詢區(qū)間變成 \([\mathrm{dfn}(u)+1, \mathrm{dfn}(v)]\),就可以求得正確的 \(d\) 了。對于情況 1,由于 \(u \neq v'\),所以還可以查詢 \([\mathrm{dfn}(u)+1, \mathrm{dfn}(v)]\)

      需要特判的是,如果 \(u = v\),直接返回 \(u\) 即可。

      至于如何查找深度最小的節(jié)點 \(v\),顯然要用 ST 表了。為了查詢方便,我們可以向 \(f_{i, 0}\) 中存入每個節(jié)點的父親,比較時取時間戳較小的節(jié)點。

      模板題(洛谷 P3379)參考代碼:

      #include <bits/stdc++.h>
      typedef long long ll;
      const int N = 5e5+10;
      int n, m, s;
      std::vector<int> g[N];
      int st[N][20], lg[N], dfn[N], dn;
      int get(int x, int y) {
        return dfn[x] < dfn[y]? x: y;
      }
      void dfs(int u, int par) {
        dn++;
        dfn[u] = dn;
        st[dn][0] = par;
        for (int &v: g[u]) {
          if (v != par) {
            dfs(v, u);
          }
        }
      }
      int lca(int u, int v) {
        if (u == v) return u;
        u = dfn[u], v = dfn[v];
        if (u > v) std::swap(u, v);
        u++; 
        int d = lg[v - u];
        return get(st[u][d], st[v - (1 << d) + 1][d]);
      }
      
      int main() {
        std::ios::sync_with_stdio(false); std::cin.tie(0);
        std::cin >> n >> m >> s;
        for (int i = 1; i <= n-1; i++) {
          int a, b;
          std::cin >> a >> b;
          g[a].push_back(b);
          g[b].push_back(a);
        }
        dfs(s, 0);
        lg[1] = 0;
        for (int i = 2; i <= n; i++) {
          lg[i] = lg[i >> 1] + 1;
        }
        for (int j = 1; j <= lg[n]; j++) {
          for (int i = 1; i + (1 << j) - 1 <= n; i++) {
            st[i][j] = get(st[i][j-1], st[i + (1 << (j - 1))][j-1]);
          }
        }
        for (int i = 1; i <= m; i++) {
          int u, v;
          std::cin >> u >> v;
          std::cout << lca(u, v) << '\n';
        }
        return 0;
      }
      

      樹剖求 LCA

      想學(xué)這個方法,首先得會樹剖。強烈推薦看一下 JZ8 的博客,他和我簡直心有靈犀啊 (JZ8 是誰?我不知道,我是永康喵喵)

      首先先進行預(yù)處理,把重鏈剖分出來。然后不停地把當(dāng)前兩個節(jié)點中深度較深的那一個跳到其所屬的重鏈的頂端,直到兩個節(jié)點處于一條鏈上,此時深度較淺的節(jié)點就是 LCA。

      配合上面的那篇博客,代碼顯然,不再贅述 (我絕對不會說是因為我太懶了沒寫)

      Tarjan 算法

      在學(xué)本算法前,請先確保自己會并查集

      Tarjan 算法是一種借助并查集離線求解 LCA 的算法。這種算法的一般流程是:對樹進行 DFS,在回溯時對當(dāng)前節(jié)點及其父節(jié)點在并查集中進行合并。如果當(dāng)前位于節(jié)點 \(u\) 時,節(jié)點 \(v\) 已被訪問,且要查詢節(jié)點 \(v\) 和節(jié)點 \(u\) 的 LCA 時,就返回 \(\mathrm{find}(u)\)

      流程非常簡單好記,但是有幾個坑點需要注意:

      1. 不要進行按秩合并,始終將并查集中子節(jié)點的 par 設(shè)置為父節(jié)點。
      2. 為了快速查詢并處理詢問,可以用一個存有 pairvector 數(shù)組來存儲詢問。記第 \(i\) 個詢問為查詢 \(u\)\(v\) 的 LCA,則 queries[u].push_back({v, i}), queries[v].push_back({u, i});
      #include <bits/stdc++.h>
      typedef long long ll;
      const int N = 5e5+10;
      int ans[N], n, m, s;
      bool vis[N];
      std::vector<int> g[N];
      std::vector<std::pair<int, int> > queries[N];
      
      struct UF {
        int par[N], siz[N];
        void init() {
          for (int i = 1; i <= n; i++) {
            par[i] = i, siz[i] = 1;
          }
        }
        int find(int u) {
          return u == par[u] ? u : (par[u] = find(par[u]));
        }
        void merge(int u, int v) {
          u = find(u), v = find(v);
          par[u] = v;
        }
      } uf;
      
      void tarjan(int u) {
        vis[u] = true;
        for (auto &v: g[u]) {
          if (vis[v]) continue;
          tarjan(v);
          uf.merge(v, u);
        }
        for (auto &q : queries[u]) {
          int v = q.first;
          int id = q.second;
          if (vis[v]) ans[id] = uf.find(v);
        }
      }
      
      int main() {
        std::ios::sync_with_stdio(false); std::cin.tie(0);
        std::cin >> n >> m >> s;
        for (int i = 1; i <= n - 1; i++) {
          int u, v;
          std::cin >> u >> v;
          g[u].push_back(v);
          g[v].push_back(u);
        }
        uf.init();
        for (int i = 1; i <= m; i++) {
          int u, v;
          std::cin >> u >> v;
          if (u == v) ans[i] = u;
          else {
            queries[u].push_back({v, i});
            queries[v].push_back({u, i});
          }
        }
        tarjan(s);
        for (int i = 1; i <= m; i++) {
          std::cout << ans[i] << '\n';
        }
        return 0;
      }
      

      比較

      下表部分由 DeepSeek 生成。

      特性 DFS 序 + RMQ 倍增法 樹鏈剖分 Tarjan
      預(yù)處理時間復(fù)雜度 \(O(n \log n)\) \(O(n \log n)\) \(O(n)\) /
      單次查詢時間復(fù)雜度 \(O(1)\) \(O(\log n)\) \(O(\log n)\) /
      整個程序時間復(fù)雜度 \(O(n \log n + m)\) \(O(nm \log n)\) \(O(nm \log n)\) \(O(n + m \alpha(n))\)
      空間復(fù)雜度 \(O(n \log n)\) \(O(n \log n)\) \(O(n)\) \(O(n)\)
      查詢常數(shù)因子 很小 中等 很小 很小
      編碼復(fù)雜度 中等 中等
      靈活性 極好
      是否支持動態(tài)樹
      擴展性 較好 極好
      是否必須離線

      注:\(\alpha(x)\)反阿克曼函數(shù),增長率極慢,一般可當(dāng)作常數(shù)看待。

      總而言之,建議初學(xué)者先學(xué)習(xí)倍增法,因為其代碼實現(xiàn)簡單、靈活性較好。如果要追求更高的性能,推薦使用樹剖法、DFS 序法或 Tarjan 算法(尤其在需要處理大量查詢時)。

      posted @ 2025-10-27 21:03  JZ8  閱讀(7)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国内不卡不区二区三区| 国产精品一区二区三区自拍| 丰满人妻被黑人猛烈进入| 动漫AV纯肉无码AV电影网| www内射国产在线观看| 绝顶丰满少妇av无码| 91精品国产自产在线蜜臀| 亚洲人成18在线看久| 亚洲中文久久久精品无码| 成年站免费网站看v片在线| 99久久亚洲综合精品成人网| 国产一区二区三区不卡视频| 免费现黄频在线观看国产| 精品国产这么小也不放过| 中国产无码一区二区三区| 四虎成人精品无码| 91老肥熟女九色老女人| 精品国产一区二区三区大| 黑人欧美一级在线视频| 亚洲av日韩av综合在线观看| h无码精品动漫在线观看| 亚洲夂夂婷婷色拍WW47| 国产一区在线播放av| 国产精品污一区二区三区| 专干老肥熟女视频网站| 性欧美vr高清极品| 日韩欧美aⅴ综合网站发布| 国产精品视频亚洲二区| 精品国产免费第一区二区三区| 国内精品自在拍精选| 蜜桃无码一区二区三区| 国产suv精品一区二区33| 国产蜜臀久久av一区二区| 欧美交a欧美精品喷水| 红桃视频成人传媒| av鲁丝一区鲁丝二区鲁丝三区| 99中文字幕精品国产| 亚洲精品美女久久久久99| 蜜桃无码一区二区三区| 午夜av高清在线观看| 丰满多毛的大隂户视频|