题目描述

时间限制:1.2s 内存限制:256.0MB

问题描述

   如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

输入格式

   第一行包含三个正整数\(N\)\(M\)\(S\),分别表示树的结点个数、询问的个数和树根结点的序号。

   接下来\(N-1\)行每行包含两个正整数\(x\)\(y\) ,表示\(x\)结点和\(y\)结点之间有一条直接连接的边(数据保证可以构成树)。

   接下来\(M\)行每行包含两个正整数\(a\)\(b\),表示询问\(a\)结点和\(b\)结点的最近公共祖先。

输出格式

   输出包含\(M\)行,每行包含一个正整数,依次为每一个询问的结果。

样例输入

 5 5 4
 3 1
 2 4
 5 1
 1 4
 2 4
 3 2
 3 5
 1 2
 4 5

样例输出

4
4
1
4
4

数据规模和约定

   对于\(30%\)的数据,\(N<=10\)\(M<=10\)

   对于\(70%\)的数据,\(N<=10000\)\(M<=10000\)

   对于\(100%\)的数据,\(N<=500000\)\(M<=500000\)

注意

   这道题里一个节点的祖先是包括自己的

解析

1.暴力(70分)

#include <iostream>
#include <cstring>
#include <vector>

using namespace std;

vector<int> a[500005];
int deep[500005], father[500005];

void dfs(int cur, int par) { // 用dfs遍历数
	father[cur] = par;    // 存父亲节点
	deep[cur] = deep[par] + 1; // 存深度
	for (int i = 0; i < a[cur].size(); i++)
		if (a[cur][i] != par)
			dfs(a[cur][i], cur);
}

int find(int x, int y) { // 寻找两个节点的直接最近公共祖先
	// 若两个节点的深度不同,先让两个节点深度相等,再一起向上寻找祖先
	if (deep[x] < deep[y]) swap(x, y);
	while (deep[x] > deep[y]) x = father[x];
	while (x != y) x = father[x], y = father[y];
	return x;
}

int main(int argc, char** argv) {
	int n, m, s, x, y; cin >> n >> m >> s;
	for (int i = 0; i < n - 1; i++) {
		cin >> x >> y;
		a[x].push_back(y);
		a[y].push_back(x);
	}
	dfs(s, 0);
	for (int i = 0; i < m; i++) {
		cin >> x >> y;
		cout << find(x, y) << endl;
	}
	return 0;
}

2.优化

  首先我们发现主要影响速度的就是find函数

  可以分成两段,并齐深度和共同向上找这两个部分

并齐深度:

我们是知道x需要向上的次数,也就是deep[x] - deep[y]次
  
可以运用二进制的思想来减少询问次数
  
定义数组f[i][t]为i节点向上寻找2^t个的祖先

处理数组用递推的形式表示出来

f[i][0] = father[i];

f[i][j] = f[f[i][j - 1]][j - 1];

当然,会出现找到的祖先以超过根节点这种问题,可以将根节点s的祖先定位它本身
  
可将deep[x] - deep[y]转化为二进制形式,bin[n]若为1,表示向上询问对应的次数

向上寻找祖先:

t从19到0

每次询问x与y向上2^t的公共祖先是否相同
  
若不相同时退出循环x的父节点则是x与y的最近公共祖先
#include <iostream>
#include <cstring>
#include <vector>

using namespace std;

vector<int> a[500005];
int deep[500005], father[500005];
int f[500005][25];
int n, s;

void dfs(int cur, int par) {
	father[cur] = par;
	deep[cur] = deep[par] + 1;
	for (int i = 0; i < a[cur].size(); i++)
		if (a[cur][i] != par)
			dfs(a[cur][i], cur);
}

void init() {
	for (int i = 1; i <= n; i++)
		f[i][0] = father[i];
	for (int j = 1; j <= 19; j++)
		for (int i = 1; i <= n; i++)
			f[i][j] = f[f[i][j - 1]][j - 1];
}

int find(int x, int y) {
	if (deep[x] < deep[y]) swap(x, y);
	int w = 0, N = deep[x] - deep[y];
	while (N) {
		if (N % 2 == 1) x = f[x][w];
		N /= 2;
		w++;
	}
	if (x == y) return x;
	for (int t = 19; t >= 0; t--){
		if (f[x][t] != f[y][t])
			x = f[x][t], y = f[y][t];
	}
	return father[x];
}

int main(int argc, char** argv) {
	ios::sync_with_stdio(false);
	int m, x, y; cin >> n >> m >> s;
	for (int i = 0; i < n - 1; i++) {
		cin >> x >> y;
		a[x].push_back(y);
		a[y].push_back(x);
	}
	dfs(s, s); init();
	for (int i = 0; i < m; i++) {
		cin >> x >> y;
		cout << find(x, y) << endl;
	}
	return 0;
}

相关文章: