Lowest Common Ancestor 最近公共祖先 过了初 中级测试,附:国外大神满分简洁代码

23 篇文章 0 订阅
11 篇文章 0 订阅

https://www.codechef.com/LTIME14/problems/TALCA

LCA:最近公共祖先,对于有根树T的两个节点u,v,最近公共祖先LCA(T,u,v)表示一个节点x,满足x是u,v的祖先且x的深度depth尽可能大。

终于把LCA这棵树给调试好了,在错误中不断修改,用了新学的离线操作,还有前向星存图,也只帮我过了中级测试,高级测试TLE,因为没有发现其中的技巧。

LCA板子

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 300111, logN = 20;
vector <int> adj[N]; //若需存边全的信息,把int换为结构体
int f[N][logN], dep[N], n;
void dfs(int u) {
	for (int i = 1; i < logN; i++)
		f[u][i] = f[f[u][i - 1]][i - 1];
	for (int i = 0; i < (int)adj[u].size(); i++) {
		int v = adj[u][i];
		if (!dep[v]) {
			f[v][0] = u;
			dep[v] = dep[u] + 1;
			dfs(v);
		}
	}
}
int lca(int u, int v) {
	if (dep[u] < dep[v]) swap(u, v);
	for (int i = logN - 1; i >= 0; i--)
		if (dep[f[u][i]] >= dep[v]) {
			u = f[u][i];
		}
	if (u == v) return u;
	for (int i = logN - 1; i >= 0; i--)
		if (f[u][i] != f[v][i]) {
			u = f[u][i];
			v = f[v][i];
		}
	return f[u][0];
}
int dist(int u, int v) { //若边全不为1,就不能用这个函数就距离了
	int x = lca(u, v);
	int res = dep[u] + dep[v] - 2 * dep[x];
	return res;
}



#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2 * 1e5 + 7, H = 20;
int n, tot, root; //树的根节点
int head[N], Head[N]; //存的边的信息,head[i]表示第i个节点的头指针
int point[N << 1]; //第i条边指向的节点
int Next[N << 1]; //第i条边的下一个指针
int anc[N][H]; //对于每一个节点v,记录anc[v][k],表示它向上走pow(2,k)步之后到达的节点
int Stack[N], dep[N];
void dfs(int root) {
	int top = 0;
	dep[root] = 1;
	for (int i = 0; i < H; ++i)
		anc[root][i] = root;
	Stack[++top] = root; //先求出anc[v][0]
	memcpy(head, Head, sizeof Head);//Head为原始的第i个节点的头指针
	while (top) {
		int x = Stack[top];
		if (x != root) {
			for (int i = 1; i < H; ++i) { //再求出其他anc[v][k]
				int y = anc[x][i - 1];
				anc[x][i] = anc[y][i - 1];
			}
		}
		for (int &i = head[x]; ~i; i = Next[i]) { //这里i为引用,会修改head
			int y = point[i];
			if (y != anc[x][0]) {
				dep[y] = dep[x] + 1;
				anc[y][0] = x;
				Stack[++top] = y;
			}
		}
		while (top && head[Stack[top]] == -1) //==-1,这个-1和初始Head有关
			top--;
	}
}
void swim(int &x, int k) { //从节点x向上移动k步,并将x赋为新走到的节点
	for (int i = 0; k > 0; ++i) {
		if (k & 1)
			x = anc[x][i];
		k /= 2;
	}
}
int lca(int x, int y) { //寻找x, y的LCA。
	int k;
	if (dep[x] > dep[y]) swap(x, y);
	swim(y, dep[y] - dep[x]); //首先利用swim将x,y调整到同一高度
	if (x == y) return x; //若x和y重合,就是我们要找的LCA
	while (true) { //否则,不断第寻找一个最小的k,使得anc[x][k] = anc[y][k]
		for (k = 0; anc[x][k] != anc[y][k]; ++k);
		if (k == 0)
			return anc[x][0];
		x = anc[x][k - 1];//新的x,y和原来的x,y有相同的LCA
		y = anc[y][k - 1];
	}
	return -1;
}
void init() {
	tot = 0;
	memset(Head, -1, sizeof Head);
}
void add(int u, int v) {
	point[tot] = v;
	Next[tot] = Head[u];
	Head[u] = tot++;
}
struct Query {
	int r, u, v, pos;
	bool operator < (const Query& tmp) const {
		return r < tmp.r;
	}
}Q[N];
int ans[N];
int main()
{
	while (~scanf("%d", &n)) {
		init();
		int u, v;
		for (int i = 1; i < n; ++i) { //读入n-1条边
			scanf("%d%d", &u, &v);
			add(u, v);
			add(v, u);//无向边
		}
		int m;
		scanf("%d", &m);
		for (int i = 0; i < m; ++i) {
			scanf("%d%d%d", &Q[i].r, &Q[i].u, &Q[i].v);
			Q[i].pos = i;
		}
		sort(Q, Q + m);
		int preRoot = -1;
		for (int i = 0; i < m; ++i) {
			if (preRoot != Q[i].r) {
				dfs(root = Q[i].r);
				preRoot = Q[i].r;
			}
			ans[Q[i].pos] = lca(Q[i].u, Q[i].v);
		}
		for (int i = 0; i < m; ++i) {
			printf("%d\n", ans[i]);
		}
	}

	return 0;
}

How to get 100 points

There are two interesting observations that you can make:

  1. Given the query "r u v" what can be the answer? The possible answers are ruvLCA(r, u)LCA(r, v)LCA(u, v)where LCA(x, y) is LCA of x and y when the tree is rooted at 1.

  2. The LCA of u and v when the root is at r is the vertex x such that the sum of shortest path from x to u, x to v and x to r is smallest.

With this two observations you need to implement two function: finding LCA and distance of the two vertices in the tree. Proof for these two observation is not hard but too long to be mentioned here. It is left as an exercise for you.


#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
#define maxn 300111
#define logN 20
vector <int> adj[maxn];
int f[maxn][logN], depth[maxn], n;
void dfs(int u) {
	for (int i = 1; i < logN; i++)
		f[u][i] = f[f[u][i - 1]][i - 1];
	for (int i = 0; i < (int)adj[u].size(); i++) {
		int v = adj[u][i];
		if (!depth[v]) {
			f[v][0] = u;
			depth[v] = depth[u] + 1;
			dfs(v);
		}
	}
}
int lca(int u, int v) {
	if (depth[u] < depth[v]) swap(u, v);
	for (int i = logN - 1; i >= 0; i--)
		if (depth[f[u][i]] >= depth[v]) {
			u = f[u][i];
		}
	if (u == v) return u;
	for (int i = logN - 1; i >= 0; i--)
		if (f[u][i] != f[v][i]) {
			u = f[u][i];
			v = f[v][i];
		}
	return f[u][0];
}
int dist(int u, int v) {
	int x = lca(u, v);
	int res = depth[u] + depth[v] - 2 * depth[x];
	return res;
}
int main() {
	ios::sync_with_stdio(false);
	cin >> n;
	for (int i = 1; i < n; i++) {
		int u, v;
		cin >> u >> v;
		adj[u].push_back(v);
		adj[v].push_back(u);
	}
	depth[1] = 1;
	dfs(1);
	int q;
	cin >> q;
	pair<int, int> p[6];
	while (q--) {
		int u, v, r;
		cin >> r >> u >> v;
		p[0].second = u;
		p[1].second = v;
		p[2].second = r;
		p[3].second = lca(r, u);
		p[4].second = lca(r, v);
		p[5].second = lca(u, v);
		for (int i = 0; i < 6; i++) {
			int x = p[i].second;
			p[i].first = dist(x, r) + dist(x, u) + dist(x, v);
		}
		sort(p, p + 6);
		cout << p[0].second << endl;
	}
}

再附我的和大神的综合一下440ms

#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2 * 1e5 + 7, H = 20;
int n, tot, root; //树的根节点
int head[N], Head[N]; //存的边的信息,head[i]表示第i个节点的头指针
int point[N << 1]; //第i条边指向的节点
int Next[N << 1]; //第i条边的下一个指针
int anc[N][H]; //对于每一个节点v,记录anc[v][k],表示它向上走pow(2,k)步之后到达的节点
int Stack[N], dep[N];
void dfs(int root) {
	int top = 0;
	dep[root] = 1;
	for (int i = 0; i < H; ++i)
		anc[root][i] = root;
	Stack[++top] = root; //先求出anc[v][0]
	memcpy(head, Head, sizeof Head);//Head为原始的第i个节点的头指针
	while (top) {
		int x = Stack[top];
		if (x != root) {
			for (int i = 1; i < H; ++i) { //再求出其他anc[v][k]
				int y = anc[x][i - 1];
				anc[x][i] = anc[y][i - 1];
			}
		}
		for (int &i = head[x]; ~i; i = Next[i]) { //这里i为引用,会修改head
			int y = point[i];
			if (y != anc[x][0]) {
				dep[y] = dep[x] + 1;
				anc[y][0] = x;
				Stack[++top] = y;
			}
		}
		while (top && head[Stack[top]] == -1) //==-1,这个-1和初始Head有关
			top--;
	}
}
void swim(int &x, int k) { //从节点x向上移动k步,并将x赋为新走到的节点
	for (int i = 0; k > 0; ++i) {
		if (k & 1)
			x = anc[x][i];
		k /= 2;
	}
}
int lca(int x, int y) { //寻找x, y的LCA。
	int k;
	if (dep[x] > dep[y]) swap(x, y);
	swim(y, dep[y] - dep[x]); //首先利用swim将x,y调整到同一高度
	if (x == y) return x; //若x和y重合,就是我们要找的LCA
	while (true) { //否则,不断第寻找一个最小的k,使得anc[x][k] = anc[y][k]
		for (k = 0; anc[x][k] != anc[y][k]; ++k);
		if (k == 0)
			return anc[x][0];
		x = anc[x][k - 1];//新的x,y和原来的x,y有相同的LCA
		y = anc[y][k - 1];
	}
	return -1;
}
void init() {
	tot = 0;
	memset(Head, -1, sizeof Head);
}
void add(int u, int v) {
	point[tot] = v;
	Next[tot] = Head[u];
	Head[u] = tot++;
}

int dist(int u, int v) {
	int x = lca(u, v);
	int res = dep[u] + dep[v] - 2 * dep[x];
	return res;
}

int main()
{
	while (~scanf("%d", &n)) {
		init();
		int u, v, r;
		for (int i = 1; i < n; ++i) { //读入n-1条边
			scanf("%d%d", &u, &v);
			add(u, v);
			add(v, u);//无向边
		}
		dfs(root = 1);
		int m, ans[6];
		scanf("%d", &m);
		pair<int, int> p[6];
		while (m--) {
			int u, v, r;
			scanf("%d%d%d", &r, &u, &v);
			p[0].second = u;
			p[1].second = v;
			p[2].second = r;
			p[3].second = lca(r, u);
			p[4].second = lca(r, v);
			p[5].second = lca(u, v);
			for (int i = 0; i < 6; i++) {
				int x = p[i].second;
				p[i].first = dist(x, r) + dist(x, u) + dist(x, v);
			}
			sort(p, p + 6);
			printf("%d\n", p[0].second);
		}
	}

	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值