索尼新專利:使用AI為PS5玩家推薦武器裝備
阿新 • • 發佈:2021-08-15
最近公共祖先(LCA)
首先是最近公共祖先的概念 (什麼是最近公共祖先? ): 在一棵沒有環的樹上,每個節點肯定有其父親節點和祖先節點,而最近公共祖先,就是兩個節點在這棵樹上深度最大的公共的祖先節點。 換句話說,就是兩個點在這棵樹上距離最近的公共祖先節點
樸素演算法
求最近公共祖先的方法有很多,想最簡單的樸素演算法,就是遍歷,從兩個點開始,每次讓深度大的先往上方跳,那麼在第一次遇見的點就是他們的最近公共祖先
前提是需要先dfs遍歷一遍樹,求出深度
倍增演算法
由於我不會Tarjan 演算法倍增演算法通過預處理fa陣列,可以快速的在樹上向上方跳,建立fa陣列fa[u][i]表示u的第2^i個祖先
dist[u][i]表示u到第2^i個祖先的距離
程式碼對應 How far away ?
#include <iostream> #include <algorithm> #include <cstring> using namespace std; typedef pair<int, int> PII; typedef long long ll; inline int read(void)//常見的讀入 { register int x = 0; register short sgn = 1; register char c = getchar(); while (c < 48 || 57 < c) { if (c == 45) sgn = 0; c = getchar(); } while (47 < c && c < 58) { x = (x << 3) + (x << 1) + c - 48; c = getchar(); } return sgn ? x : -x; } inline void write(ll x)//沒有特點的輸出 { if (x < 0) putchar('-'), x = -x; if (x > 9) write(x / 10); putchar(x % 10 + '0'); } const int N = 4e4 + 10, M = 2 * N; int T, n, m; int h[N], e[M], ne[M], w[M], idx;//鄰接表存圖 int fa[N][31], dist[N][31], dep[N];//fa[u][i]表示u的第2^i個祖先,dist[u][i]表示u到第2^i個祖先的距離,dep[u]表示u的深度 inline void add (int a, int b, int c) {//加邊函式 e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } void dfs (int u, int father) { fa[u][0] = father;//當前點的第2^0個祖先就是我的父親 dep[u] = dep[fa[u][0]] + 1;//當前點的深度就是父親的深度 + 1 for (int i = 1;i < 31;i ++) { //u的第2^i個祖先,就是第2^(i - 1)個祖先的第2^(i - 1)個祖先 fa[u][i] = fa[fa[u][i - 1]][i - 1]; //u到第2^i個祖先的距離就是u到第2^(i - 1)個祖先的距離加上第2^(i - 1)個祖先到他的2^(i - 1)個祖先的距離 dist[u][i] = dist[u][i - 1] + dist[fa[u][i - 1]][i - 1]; } for (int i = h[u];~i;i = ne[i]) {//遍歷子節點 int j = e[i]; if (j == father) continue;//由於是無向圖,防止往回搜 dist[j][0] = w[i];//從u到v,那麼v到第2^0個祖先,也就是v到父親的距離,就是這條邊的長度 dfs(j, u); } } int lca (int x, int y) { int res = 0; if(dep[x] > dep[y]) swap(x, y);//令y為深度大的點 int tmp = dep[y] - dep[x];//得到深度差 for (int j = 0;tmp;j ++, tmp >>= 1) {//二進位制遞減深度 if(tmp & 1) res += dist[y][j], y = fa[y][j];//二進位制累加、遞推 } if (x == y) return res;//如果x == y,證明x和y在同一個小子樹上,直接返回結果 //如果不在一個小子樹上,繼續往上找 for (int i = 30;i >= 0;i --) {//這裡從上往下,使得x和y到距離最近公共祖先最近的點 if (fa[x][i] != fa[y][i]) { res += dist[x][i] + dist[y][i]; x = fa[x][i]; y = fa[y][i]; } } res += dist[x][0] + dist[y][0];//最後把從x和y到最近公共祖先的距離也加上 return res; } int main() { //freopen("in.txt", "r", stdin); //freopen("out.txt", "w", stdout); T = read(); while (T --) { memset(h, -1, sizeof h); n = read(), m = read(); for (int i = 1;i < n;i ++) { int a = read(), b = read(), c = read(); add(a, b, c);//無向圖,存兩條邊 add(b, a, c); } dfs(1, 0); while (m --) { int a = read(), b = read(); write(lca(a, b)); puts(""); } } return 0; }
如果是為了求最近公共祖先的點,就可以把求距離的全部刪掉了
程式碼對應 P3379 【模板】最近公共祖先(LCA)
#include <iostream> #include <algorithm> #include <cstring> using namespace std; typedef pair<int, int> PII; typedef long long ll; inline int read(void) { register int x = 0; register short sgn = 1; register char c = getchar(); while (c < 48 || 57 < c) { if (c == 45) sgn = 0; c = getchar(); } while (47 < c && c < 58) { x = (x << 3) + (x << 1) + c - 48; c = getchar(); } return sgn ? x : -x; } inline void write(ll x) { if (x < 0) putchar('-'), x = -x; if (x > 9) write(x / 10); putchar(x % 10 + '0'); } const int N = 4e4 + 10, M = 2 * N; int T, n, m; int h[N], e[M], ne[M], idx;//存圖 int fa[N][31], dist[N][31], dep[N];//fa[u][i]表示u的第2^i個祖先,dist[u][i]表示u到第2^i個祖先的距離,dep[u]表示u的深度 inline void add (int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } void dfs (int u, int father) { fa[u][0] = father;//當前點的第2^0個祖先就是我的父親 dep[u] = dep[fa[u][0]] + 1;//當前點的深度就是父親的深度 + 1 for (int i = 1;i < 31;i ++) { fa[u][i] = fa[fa[u][i - 1]][i - 1];//u的第2^i個祖先,就是第2^(i - 1)個祖先的第2^(i - 1)個祖先 } for (int i = h[u];~i;i = ne[i]) {//遍歷子節點 int j = e[i]; if (j == father) continue; dfs(j, u); } } int lca (int x, int y) { if(dep[x] > dep[y]) swap(x, y);//令y為深度大的點 int tmp = dep[y] - dep[x];//得到深度差 for (int j = 0;tmp;j ++, tmp >>= 1) {//二進位制遞減深度 if(tmp & 1) y = fa[y][j]; } if (x == y) return x; //如果不在一個小子樹上,繼續往上找 for (int i = 30;i >= 0;i --) {//這裡從上往下,使得x和y到距離最近公共祖先最近的點 if (fa[x][i] != fa[y][i]) { x = fa[x][i]; y = fa[y][i]; } } return fa[x][0]; } int main() { freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); // T = read(); // while (T --) { memset(h, -1, sizeof h); n = read(), m = read(); int root = read(); for (int i = 1;i < n;i ++) { int a = read(), b = read(); add(a, b); add(b, a); } dfs(root, 0); while (m --) { int a = read(), b = read(); write(lca(a, b)); puts(""); } // } return 0; }
等我學習了Tarjan演算法我還會回來的