圖論學習筆記——LCA
阿新 • • 發佈:2020-10-27
LCA
解法一:Tarjan(O(n+q))
思路:
初始化fa[i]=i
,vis[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\)
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; }