1. 程式人生 > 遊戲 >索尼新專利:使用AI為PS5玩家推薦武器裝備

索尼新專利:使用AI為PS5玩家推薦武器裝備

最近公共祖先(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演算法我還會回來的