[hiho 15]最近公共祖先 二

题目描述

这次使用离线算法来解决最近公共祖先的问题。

离线算法可以一遍 dfs 处理完所有的查询,因而需要把查询全部储存起来。

具体的 dfs 过程是:

所有节点最初标记为白色,第一次经过该节点时,将其染成灰色,第二次经过该节点时(即离开该节点时)将其染成黑色。

在 dfs 的某个状态下,白色代表未访问的节点,黑色代表已经访问完该节点为根整个子树,灰色代表正在访问该节点为根的子树。

可以在每个节点处查询与该节点相关的所有查询,每个查询对应有另一个节点X(可能是自身):

如果X是白色,说明还未访问到,信息不全,以后再处理;

如果X是灰色,说明当前节点是X子树中的元素,那么它们的最近公共祖先就是X;

如果X是黑色,可以寻找X的一个最近的灰色祖先节点Y,当前节点是Y的子树中的元素,X和当前节点的最近公共祖先就是Y。

 

如何查询与特定节点相关的所有查询呢?可以使用vector存下来。

如何寻找X的一个最近的灰色祖先节点Y呢?可以用并查集。

 

使用这种方法,每个查询实际上会被处理一次(白、黑/灰)或两次(灰、黑)。复杂度是 O(m) (m为查询量)的。

 

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

#define MAX 100005
#define MAX_HASH  200005

using namespace std;

char names[MAX_HASH][50];

vector<int> sons[MAX_HASH];
int root[MAX_HASH];
int color[MAX_HASH];

int query[MAX][2];
vector<int> i_query[MAX_HASH];
int ans[MAX];

int n;

unsigned int BKDRHash(char *str)
{
    unsigned int seed = 131;
    unsigned int hash = 0;

    while (*str)
    {
        hash = hash * seed + (*str++);
    }

    return (hash & 0x7FFFFFFF) % MAX_HASH;
}

int get_index(char *str) {
	int res = BKDRHash(str);
	while (strlen(names[res]) != 0 && strcmp(str, names[res])) {
		res++;
		if (res > MAX_HASH) res -= MAX_HASH;
	}
	if (strlen(names[res]) == 0) {
		strcpy(names[res], str);
	}
	return res;
}

int get_root(int i) {
	return color[i] == 1 ? i : root[i] = get_root(root[i]);
}

void dfs(int root_idx) {
	color[root_idx] = 1;
	int len = i_query[root_idx].size();
	for (int i = 0; i < len; i++) {
		int q_idx = i_query[root_idx][i];
		int another = root_idx == query[q_idx][0] ? query[q_idx][1] : query[q_idx][0];
		if (color[another] == 1) {
			ans[q_idx] = another;
		}
		else if (color[another] == 2 && ans[q_idx] == 0) {
			ans[q_idx] = get_root(another);
		}
	}
	int son_len = sons[root_idx].size();
	for (int i = 0; i < son_len; i++) {
		dfs(sons[root_idx][i]);
	}
	color[root_idx] = 2;
}

int main() {
	char s1[50], s2[50];
	int i1, i2;
	int start;

	cin >> n;
	cin >> s1 >> s2;
	i1 = get_index(s1);
	i2 = get_index(s2);
	sons[i1].push_back(i2);
	root[i2] = i1;
	start = i1;
	while (--n) {
		cin >> s1 >> s2;
		i1 = get_index(s1);
		i2 = get_index(s2);
		sons[i1].push_back(i2);
		root[i2] = i1;
	}

	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> s1 >> s2;
		i1 = get_index(s1);
		i2 = get_index(s2);
		query[i][0] = i1;
		query[i][1] = i2;
		i_query[i1].push_back(i);
		i_query[i2].push_back(i);	
	}

	dfs(start);

	for (int i = 0; i < n; i++) {
		cout << names[ans[i]] << endl;
	}
	return 0;
}

转载于:https://www.cnblogs.com/xblade/p/4488835.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值