1. 程式人生 > 實用技巧 >圖論學習筆記——LCA

圖論學習筆記——LCA

LCA

解法一:Tarjan(O(n+q))

思路:
初始化fa[i]=ivis[i]=0.

從樹根開始DFS,標記當前搜尋中的節點\(v\)vis[v] = -1。然後搜尋其滿足\(vis[i]=0\)的所有子節點\(i\)。當一個節點\(v\)的所有子節點均被搜尋過(或無子節點)時,則用並查集的合併操作,令\(fa[v]\)為其祖先節點。然後開始查詢與節點\(v\)有詢問關係的節點\(q\),若\(q\)已被搜尋過(vis[q] = 1),則\(LCA(v,q)=find(q)\)。遍歷完有詢問關係的節點後,標記\(v\)已被搜尋完畢vis[v] = 1

離線操作,將\(q\)個詢問以鏈式前向星雙向邊的形式儲存,也就是說共\(2q\)

條邊。當\(i\)為奇數時,\(i\)\(i+1\)是同樣兩個節點,答案應該一致,所以在記錄答案時程式碼如下:

for (int i = headq[u]; i; i = eq[i].next) {
        int v = eq[i].to;
        if (vis[v] == 1) {
            lca[i] = find(v);
            if (i % 2) lca[i + 1] = lca[i];
            else lca[i - 1] = lca[i];
        }
    }

完整程式碼:

const int INF = 0x3f3f3f;
const int maxn = 2e6 + 100;

struct Edge {
    int from, to, next; LL w;
}e[maxn * 4], eq[maxn * 4];

int cnt_e = 0, head[maxn], headq[maxn], n, m;
int vis[maxn], fa[maxn];
int lca[maxn];

void add(int head[], Edge e[], int u, int v) {
    e[++cnt_e].next = head[u]; e[cnt_e].from = u; e[cnt_e].to = v; head[u] = cnt_e;
}

int find(int x) {
    return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void merge(int x, int y) {
    int a = find(x);
    int b = find(y);
    if (a != b) {
        fa[a] = b;
    }
}

void tarjan(int u) {
    vis[u] = -1;
    //標記u正在搜尋,防止來回dfs死迴圈
    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].to;
        if (!vis[v]) {
            //只有未搜尋過的子節點才能繼續搜尋
            tarjan(v);
            merge(v, u);
        }
    }
    //查詢與u有詢問關係的節點
    for (int i = headq[u]; i; i = eq[i].next) {
        int v = eq[i].to;
        if (vis[v] == 1) {
            //v已經搜尋過,則u和v的最近公共祖先就是v的祖先
            lca[i] = find(v);
            //存詢問的時候存了雙向邊,兩條邊答案應該一致
            if (i % 2) lca[i + 1] = lca[i];
            else lca[i - 1] = lca[i];
        }
    }
    vis[u] = 1;
    //標記節點u(及其所有子節點)已經搜尋完畢
}

int main() {
    int q, s;
    cin >> n >> q >> s;
    for (int i = 1; i <= n; i++) fa[i] = i;
    for (int i = 1; i <= n - 1; i++) {
        int u, v;
        cin >> u >> v;
        add(head, e, u, v);
        add(head, e, v, u);
    }
    cnt_e = 0;
    //建新圖之前記得初始化這個
    for (int i = 1; i <= q; i++) {
        int u, v;
        cin >> u >> v;
        add(headq, eq, u, v);
        add(headq, eq, v, u);
    }
    tarjan(s);
    for (int i = 1; i <= q; i++) {
        cout << lca[i*2] << endl;
    }
    return 0;
}

解法二:倍增

解法三:RMQ

解法四:樹鏈剖分