最近公共祖先(LCA)

倍增法

1、通过一个倍增数组来向根节点预处理出以每个顶点不同深度的值,然后在通过两点之间的深度差值化为等深度,最后得到最近公共祖先的节点,也可得到两点的最短路径值。

2、时间复杂度:\(O(nlogn)\),空间复杂度:\(O(nlogn)\)

3、模版:洛谷P3379

4、倍增算法求 \(lca\)(也可以用来求解两点之间最短路径上的 \(rmq\))等等

template<typename T>
struct bei_lca{
	struct node{
		int to, nxt;
		T w;
        node(int to = 0, int nxt = 0, T w = 0) : to(to), nxt(nxt), w(w) {}
	};
	
    static const int MX = 21;
	int n, m, cnt, root;
	vector<int> dep, head;
	vector<node> edg;
	vector<array<int, MX>> fa;// 不同距离的祖先
	vector<array<T, MX>> cost;// 该路径边权值之和
	
    bei_lca() {}
	bei_lca(int n_, int m_, int root_ = 1) : dep(n_ + 1), head(n_ + 1), edg(m_ << 1 | 1), fa(n_ + 1), cost(n_ + 1) {
		cnt = 0;
        n = n_;
		m = m_;
        root = root_;
	}
	
	inline void add(int u, int v, const T &w) {
		++cnt;
		edg[cnt].to = v;
		edg[cnt].nxt = head[u];
		edg[cnt].w = w;
		head[u] = cnt;
	}

    inline void init(int n_, int m_, int root_ = 1) {
        cnt = 0;
        n = n_;
		m = m_;
        root = root_;
        dep.assign(n_ + 1, 0);
        head.assign(n_ + 1, 0);
        edg.assign(m_ << 1 | 1, node());
        fa.assign(n_ + 1, array<int, MX>());
        cost.assign(n_ + 1, array<T, MX>());
	}
	
	inline void dfs(int u, int pa) {
        fa[u][0] = pa;
        dep[u] = dep[pa] + 1;
		for (int i = 1; i < MX; i++) {
			fa[u][i] = fa[fa[u][i - 1]][i - 1];
			cost[u][i] = cost[fa[u][i - 1]][i - 1] + cost[u][i - 1];
		}
		
		for (int i = head[u]; i; i = edg[i].nxt) {
			int v = edg[i].to;
			T w = edg[i].w;
			if (v == pa) {
                continue;
            }
			cost[v][0] = w;
			dfs(v, u);
		}
	}
	
	// inline T lca(int x, int y) {// 查询两个点的最短路径
	// 	if (dep[x] < dep[y]) {
	// 		swap(x, y);
	// 	}
		
	// 	int depth = dep[x] - dep[y];
	// 	T ans = 0;
	// 	for (int i = 0; depth; ++i, depth >>= 1) {
	// 		if (depth & 1) {
	// 			ans += cost[x][i];
	// 			x = fa[x][i];
	// 		}
	// 	}
		
	// 	for (int i = MX; i >= 0; i--) {
	// 		if (fa[x][i] != fa[y][i]) {
	// 			ans += cost[x][i] + cost[y][i];
	// 			x = fa[x][i];
	// 			y = fa[y][i];
	// 		}
	// 	}
		
    //     if (x != y) {
    //         ans += cost[x][0] + cost[y][0];
    //         x = fa[x][0];
    //         y = fa[y][0];// 公共祖先
	// 	}
		
	// 	return ans;
	// }

     inline int lca(int x, int y) {// 查找两个点的最近公共祖先
		if (dep[x] < dep[y]) {
			swap(x, y);
		}
		
		int depth = dep[x] - dep[y];
		for (int i = 0; depth; ++i, depth >>= 1) {
			if (depth & 1) {
				x = fa[x][i];
			}
		}
		
		for (int i = MX - 1; i >= 0; i--) {
			if (fa[x][i] != fa[y][i]) {
				x = fa[x][i];
				y = fa[y][i];
			}
		}
		
        if (x != y) {
		    x = fa[x][0];
		    y = fa[y][0];// 公共祖先
		}
		return x;
	}
};

void solve() {
	int n, m, s;
	std::cin >> n >> m >> s;
	bei_lca<i64> tr(n, n - 1, s);
    for (int i = 1, u, v; i < n; i++) {
        cin >> u >> v;
        tr.add(u, v, 1);
        tr.add(v, u, 1);
    }
    tr.dfs(s, 0);
	for (int i = 1; i <= m; i++) {
		int x, y;
		std::cin >> x >> y;
		std::cout << tr.lca(x, y) << '\n';
	}
}

注:这里的所求的最短路是针对于边权来实现的。

欧拉序列

1、通过欧拉序列(也叫 \(dfn\) 序列)的特殊性质,像 \(ST\) 表一样预处理出一些有必要的区间(同样也是基于倍增算法实现的),这里需要维护两个数组,一个代表这个区间内的最小深度,一个维护的是这个最小深度中的节点编号,这样就可以得到两个节点的最近公共祖先了。

2、如图:

3、时间复杂度:\(O(nlogn)\),空间复杂度:\(O(nlogn)\)

4、模版:洛谷P3379

int lg[N + 1];
void init() {
    for (int i = 2; i <= N; i++) {
        lg[i] = lg[i >> 1] + 1;
    }
}

template<typename T>
struct Oula_lca{
	struct node{
		int to, nxt;
		T w;
        node(int to = 0, int nxt = 0, T w = 0) : to(to), nxt(nxt), w(w) {}
	};
	
    static const int MX = 20;
	int n, m, cnt, tot, root;
	vector<int> dep, dfn, pos, head;
	vector<node> edg;
	vector<array<int, MX>> depmn, rev;
	
    Oula_lca() {}
	Oula_lca(int n_, int m_, int root_) : dep(n_ << 1 | 1), dfn(n_ << 1 | 1), pos(n_ + 1), head(n_ + 1), edg(m_ << 1 | 1), depmn(n_ << 1 | 1), rev(n_ << 1 | 1) {
		n = n_;
		m = m_;
		cnt = 0;
		tot = 0;
		root = root_;
	}
	
	inline void add(int u, int v, const T &w) {
		++cnt;
		edg[cnt].to = v;
		edg[cnt].nxt = head[u];
		edg[cnt].w = w;
		head[u] = cnt;
	}

    inline void init(int n_, int m_, int root_ = 1) {
        n = n_;
		m = m_;
		cnt = 0;
		tot = 0;
		root = root_;
        dep.assign(n_ << 1 | 1, 0);
        dfn.assign(n_ << 1 | 1, 0);
        pos.assign(n_ + 1, 0);
        head.assign(n_ + 1, 0);
        edg.assign(m_ << 1 | 1, node());
        depmn.assign(n_ << 1 | 1, array<int, MX>());
        rev.assign(n_ << 1 | 1, array<int, MX>());
	}
	
	inline void dfs(int u, int depth) {
		pos[u] = ++tot;
		dfn[tot] = u;
		dep[tot] = depth;
		for (int i = head[u]; i; i = edg[i].nxt) {
			int v = edg[i].to;
			i64 w = edg[i].w;
			if (pos[v]) {
                continue;
            }
			dfs(v, depth + 1);
			dfn[++tot] = u;
			dep[tot] = depth;
		}
	}

    inline void deal() {// 预处理
        dfs(root, 0);
		for (int i = 1; i <= tot; i++) {
			depmn[i][0] = dep[i];
			rev[i][0] = dfn[i];
		}
		for (int i = 1; i <= lg[tot]; i++) {
			for (int j = 1; j + (1 << i) <= tot + 1; j++) {
				if (depmn[j][i - 1] < depmn[j + (1 << (i - 1))][i - 1]) {
					depmn[j][i] = depmn[j][i - 1];
					rev[j][i] = rev[j][i - 1];
				} else {
					depmn[j][i] = depmn[j + (1 << (i - 1))][i - 1];
					rev[j][i] = rev[j + (1 << (i - 1))][i - 1];
				}
			}
		}
    }
	
	inline int lca(int x, int y) {// 最近公共祖先
		if (x == y) return x;
		int l = pos[x], r = pos[y];
		if (l > r) {
			swap(l, r);
		}
		int le = lg[r - l + 1];
		return (depmn[l][le] < depmn[r - (1 << le) + 1][le] ? rev[l][le] : rev[r - (1 << le) + 1][le]);
	}
};

void solve() {
	int n, m, root;
	std::cin >> n >> m >> root;
	Oula_lca<int> tr(n, n - 1, root);
    for (int i = 1, u, v; i < n; i++) {
        cin >> u >> v;
        tr.add(u, v, 1);
        tr.add(v, u, 1);
    }
    tr.deal();
	for (int i = 1; i <= m; i++) {
		int x, y;
		std::cin >> x >> y;
		std::cout << tr.lca(x, y) << '\n';
	}
}

树链剖分

1、利用重儿子的性质将整个数分为 \(logn\) 块,每块上深度最小的那个点为 \(top\),查询公共祖先时,可以直接通过两个节点 \(x、y\)\(top[x]、top[y]\) 谁的深度大就取哪个的父亲节点,直到找到最后相等为止。

2、时间复杂度:\(O(nlogn)\),空间复杂度:\(O(n)\)

3、模版:洛谷P3379

4、树链剖分是我们目前所讲的求最近公共祖先中时间最快的,比其他两个都来得快,它可用于求解树上问题,例如:线段树的维护,这个我们后面的篇章会将。

template<typename T>
struct Sp_lca{
	struct node{
		int to, nxt;
		T w;
        node(int to = 0, int nxt = 0, T w = 0) : to(to), nxt(nxt), w(w) {}
	};
	
	int n, m, cnt, root;
	vector<node> edg;
	vector<int> dep, fa, son, siz, top, head;// 深度、父亲节点、重儿子、子树大小、所处在的重链中的最远祖先
    Sp_lca() {}
	Sp_lca(int n_, int m_, int root_ = 1) : dep(n_ + 1), fa(n_ + 1), son(n_ + 1), siz(n_ + 1), top(n_ + 1), head(n_ + 1), edg(m_ << 1 | 1) {
		n = n_;
		m = m_;
		root = root_;
		cnt = 0;
	}
	
	inline void add(int u, int v, const T &w) {
		++cnt;
		edg[cnt].to = v;
		edg[cnt].nxt = head[u];
		edg[cnt].w = w;
		head[u] = cnt;
	}
	
    inline void init(int n_, int m_, int root_ = 1) {
        n = n_;
		m = m_;
		root = root_;
		cnt = 0;
		dep.assign(n_ + 1, 0);
        fa.assign(n_ + 1, 0);
        son.assign(n_ + 1, 0);
        siz.assign(n_ + 1, 0);
        top.assign(n_ + 1, 0);
        head.assign(n_ + 1, 0);
        edg.assign(m_ << 1 | 1, node());
	}

	inline void dfs1(int u, int pa) {
		fa[u] = pa;
		siz[u] = 1;
		dep[u] = dep[pa] + 1;
		for (int i = head[u]; i; i = edg[i].nxt) {
			int v = edg[i].to;
			T w = edg[i].w;
			if (v == pa) {
				continue;
			}
			dfs1(v, u);
			siz[u] += siz[v];
			if (siz[v] > siz[son[u]]) {
				son[u] = v;
			}
		}
	}
	
	inline void dfs2(int u, int topx) {
		top[u] = topx;
		if (son[u]) {
			dfs2(son[u], topx);
		} else {
			return;
		}
		
		for (int i = head[u]; i; i = edg[i].nxt) {
			int v = edg[i].to;
			T w = edg[i].w;
			if (v == fa[u] || v == son[u]) {
				continue;
			}
			dfs2(v, v);
		}
	}

    inline void deal() {
        dfs1(root, 0);
		dfs2(root, root);
    }
	
	inline int lca(int x, int y) {
		while (top[x] != top[y]) {
			if (dep[top[x]] < dep[top[y]]) {
				swap(x, y);
			}
			x = fa[top[x]];
		}
		return dep[x] < dep[y] ? x : y;
	}
};

void solve() {
	int n, m, root;
	std::cin >> n >> m >> root;
	Sp_lca<int> tr(n, n - 1, root);
    for (int i = 1, u, v; i < n; i++) {
        std::cin >> u >> v;
        tr.add(u, v, 1);
        tr.add(v, u, 1);
    }
    tr.deal();
	for (int i = 1; i <= m; i++) {
		int x, y;
		std::cin >> x >> y;
		cout << tr.lca(x, y) << '\n';
	}
}

四毛子算法

日后更新 dog

posted @ 2024-08-28 18:44  grape_king  阅读(25)  评论(0)    收藏  举报