hiho_1067_最近公共祖先2

题目大意

给出一棵家谱树,树中的节点都有一个名字,保证每个名字都是唯一的,然后进行若干次查询,找出两个名字的最近公共祖先。 
题目链接最近公共祖先

分析

数据量大,根据题目提示,采用Tarjan + 并查集算法,进行离线LCA查询操作。即先将所有的查询存储下来,然后统一DFS遍历一遍家族树,在遍历的过程中对遍历到的当前节点相关的那些查询进行设置答案。遍历完整棵树之后,再输出答案。 
    从根节点开始遍历,对树中的每个节点都设置一个颜色(白色表示未被访问,灰色表示DFS过程中进入到该节点所在的子树,还是没有从该节点所在的子树离开;黑色表示离开该节点所在的子树)。对当前正在访问的节点,则可以对和该节点相关的那些查询进行回复(只是得到答案,并存储下来,遍历完整个树后统一回复): 
记当前节点为node1, 如果某个查询需要得到node1和node2的LCA,判断node2的颜色, 
    如果为白色,表示node2还没有被访问过,则此次先不用回复,等到node2被访问到的时候,再进行回复(那时候 node1的颜色和node2的颜色均不为白色); 
    如果为灰色,表示node2在node1先前被经过,且还没有结束,画图可知,node2就是node1和node2的LCA。 
    如果为黑色,那么需要从node2向上查找一个最低的灰色节点,且该节点就在node1到根节点的路径上。那个灰色节点就是node1和node2的LCA。为了加快node2上方的最低灰色节点的查找,使用并查集: 
    一个节点的子节点node的初始root为子节点本身,当子节点在被访问完(颜色被设置为黑色)之后,将子节点的root设置为node。那么,一个黑色节点的root就是它上方最低的那个灰色节点!

做题中间犯了一些错误: 
    没有考虑到可能多个查询的内容相同;开始做的时候,存储了每个节点相关的查询的节点unordered_map> ,想着这样在Tarjan遍历到该节点的时候,直接从vector中找到该节点相关的查询的节点。 
    然后对于每个查询对 person1,person2,映射到一个key(person1.id * MAX + person2.id), 然后对应到一个value(即查询的序号),想着这样在进行Tarjan遍历到节点时候,通过节点person1,找到它相关的各个person2,然后找到key,再找到查询的序号 num,将查到的结果放到 resultt[num]中。 
    这样想法挺好啊,可是如果多个查询的内容相同,则歇菜了。。。修改的方法是,对于每个查询对,维护一个vector,存放和它相关的各个查询的序号。改动比较麻烦,就换了另外一种存储方法。

#include<iostream>
#include<string.h>
#include<iostream>
#include<queue>
#include<unordered_map>
#include<unordered_set>
#include<string>
#include<vector>
using namespace std;
unordered_map<string, int> gName2node;
unordered_map<int, string> gNode2name;
unordered_map<int, vector<int>> gNodeQueryIds; //对应每个节点,它所相关的查询的id
vector<pair<int, int>> gQueries;	//查询,表示查询的两个人的id
vector<string> gQueryResult;
const int kMax = 200005;
int gRoot[kMax];

struct Edge{
	int to;
	int next;
};
Edge gEdges[kMax];
int gEdgeIndex;
int gHead[kMax];
int gNodeColor[kMax];

void InsertEdge(int u, int v){
	int e = gEdgeIndex++;
	gEdges[e].to = v;
	gEdges[e].next = gHead[u];
	gHead[u] = e;
}
int GetRoot(int a){
	if (gRoot[a] == a)
		return a;
	return gRoot[a] = GetRoot(gRoot[a]);
}

void Union(int a, int b){
	int p1 = GetRoot(a);
	int p2 = GetRoot(b);
	gRoot[p2] = p1;
}

void AddPerson(string person){
	if (gName2node.find(person) == gName2node.end()){
		int node = gName2node.size();
		gName2node[person] = node;
		gNode2name[node] = person;
	}
}
void Init(int n){
	gEdgeIndex = 0;
	memset(gEdges, -1, sizeof(gEdges));
	memset(gHead, -1, sizeof(gHead));
	memset(gNodeColor, 0, sizeof(gNodeColor));
	for (int i = 0; i <= 2*n; i++){
		gRoot[i] = i;
	}
}
void Tarjan(int node){
	gNodeColor[node] = 1;

	for (int e = gHead[node]; e != -1; e = gEdges[e].next){
		int v = gEdges[e].to;
		Tarjan(v);
		gRoot[v] = node;
	}

	if (! gNodeQueryIds[node].empty()){
		for (auto it = gNodeQueryIds[node].begin(); it != gNodeQueryIds[node].end(); ++it){
			int query_id = *it;
			int node2;
			if (node == gQueries[query_id].first)
				node2 = gQueries[query_id].second;
			else
				node2 = gQueries[query_id].first;
			if (gNodeColor[node2] == 0) //还没有被访问过
				continue;
			if (gNodeColor[node2] == 1){
				//节点node2在节点node之前被访问,且访问未结束,则可以确定node2在node到根节点的路径上
				gQueryResult.at(query_id) = gNode2name[node2];
				//cout << "assign, = " << gQueryResult.at(gQueryResultIndex[node*kMax + node2]) << endl;
			}
			else{//节点node2已经被访问过,且访问结束,那么node2节点所在集合的根节点(并查集的根)就是最低公共祖先
				int lca = GetRoot(node2);
				gQueryResult.at(query_id) = gNode2name[lca];
			}
		}
	}

	gNodeColor[node] = 2;
}
int main(){
	int n, n1, n2;
	string person1, person2;
	cin >> n;
	Init(n);
	for (int i = 0; i < n; i++){
		cin >> person1 >> person2;
		AddPerson(person1);
		AddPerson(person2);
		n1 = gName2node[person1];
		n2 = gName2node[person2];
		InsertEdge(n1, n2);
	}
	int m;
	cin >> m;
	gQueryResult.assign(m, "");
	for (int i = 0; i < m; i++){
		cin >> person1 >> person2;
		n1 = gName2node[person1];
		n2 = gName2node[person2];
		gQueries.push_back(pair<int, int>(n1, n2));
		gNodeQueryIds[n1].push_back(i);
		gNodeQueryIds[n2].push_back(i);
	}
	Tarjan(0);
	for (int i = 0; i < m; i++){
		cout << gQueryResult[i] << endl;
	}
	return 0;
}
时间: 2024-10-09 20:50:46

hiho_1067_最近公共祖先2的相关文章

【洛谷P3379】【模板】最近公共祖先(LCA)

题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行每行包含两个正整数a.b,表示询问a结点和b结点的最近公共祖先. 输出格式: 输出包含M行,每行包含一个正整数,依次为每一个询问的结果. 输入输出样例 输入样例#1: 5 5 4 3 1 2 4 5

50、树中两个节点的公共祖先

详细的询问: 1.该树是二叉查找树? 最近公共祖先----二叉查找树:(http://www.lintcode.com/problem/lowest-common-ancestor/) 思路:利用左子树特点:左子树 < 根 <= 右,输入节点跟根节点比较,都小于,在左子树,都大约右子树,递归的去遍历:找到当前节点在两个输入大小之间,当前节点就是. 递归和非递归 public class Solution { public TreeNode lowestCommonAncestor(TreeNo

[最近公共祖先] POJ 3728 The merchant

The merchant Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 4556   Accepted: 1576 Description There are N cities in a country, and there is one and only one simple path between each pair of cities. A merchant has chosen some paths and w

lca最近公共祖先(st表)

大体思路 1.求出每个元素在树中的深度 2.用st表预处理的方法处理出f[i][j],f[i][j]表示元素i上方第2^j行对应的祖先是谁 3.将较深的点向上挪,直到两结点的深度相同 4.深度相同后,祖先可能就在上方,再走几步就到了,于是两个点同时向上移 具体的方法和代码贴在下面 ↓ 具体来看 1.求出每个元素在树中的深度 //求每个节点在树中的深度 void dfs(int pos,int pre)//pre是pos的父节点 { for(int i=0;i<v[pos].size;i++)//

LCA(最近公共祖先)--tarjan离线算法 hdu 2586

HDU 2586 How far away ? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 11320    Accepted Submission(s): 4119 Problem Description There are n houses in the village and some bidirectional roads c

最近公共祖先 LCA Tarjan算法

来自:http://www.cnblogs.com/ylfdrib/archive/2010/11/03/1867901.html 对于一棵有根树,就会有父亲结点,祖先结点,当然最近公共祖先就是这两个点所有的祖先结点中深度最大的一个结点. 0 | 1 /   \ 2      3 比如说在这里,如果0为根的话,那么1是2和3的父亲结点,0是1的父亲结点,0和1都是2和3的公共祖先结点,但是1才是最近的公共祖先结点,或者说1是2和3的所有祖先结点中距离根结点最远的祖先结点. 在求解最近公共祖先为问

最近公共祖先

0. 概要 最近公共祖先,指的是在一颗有根树上,两个点的公共祖先中,深度最大的那个. 最直接的应用是求无权树上两个点的最短距离:$distance(u, v)  = depth(u) + depth(v) - 2depth(lca(u, v))$. 再有其他的应用则以后再提. 1 基于 dfs 序列上 RMQ 的稀疏表解法 首先 dfs 遍历树,如下如图中蓝色箭头的顺序.并记录: 1. 遍历点序列 $euler[] = \{1, 2, 1, 3, 5, 3, 6 ……$ 2. 每个点首次在 eu

LeetCode OJ:Lowest Common Ancestor of a Binary Tree(最近公共祖先)

Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes v and w as the lowest node in T that has both v and w

最近公共祖先(三种算法)

最近研究了一下最近公共祖先算法,根据效率和实现方式不同可以分为基本算法.在线算法和离线算法.下面将结合hihocoder上的题目分别讲解这三种算法. 1.基本算法 对于最近公共祖先问题,最容易想到的算法就是从根开始遍历到两个查询的节点,然后记录下这两条路径,两条路径中距离根节点最远的节点就是所要求的公共祖先. 题目参见 #1062 : 最近公共祖先·一 附上AC代码,由于记录的方式采取的是儿子对应父亲,所以实现的时候有点小技巧,就是对第一个节点的路径进行标记,查找第二个节点的路径时一旦发现访问到