题目大意

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

分析

数据量大,根据题目提示,采用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;
}

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

  1. LCA最近公共祖先 ST+RMQ在线算法

    对于一类题目,是一棵树或者森林,有多次查询,求2点间的距离,可以用LCA来解决.     这一类的问题有2中解决方法.第一种就是tarjan的离线算法,还有一中是基于ST算法的在线算法.复杂度都是O( ...

  2. 【转】最近公共祖先(LCA)

    基本概念 LCA:树上的最近公共祖先,对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u.v的祖先且x的深度尽可能大. RMQ:区间最小值查询问题.对于长度为n的 ...

  3. 【并查集】【树】最近公共祖先LCA-Tarjan算法

    最近公共祖先LCA 双链BT 如果每个结点都有一个指针指向它的父结点,于是我们可以从任何一个结点出发,得到一个到达树根结点的单向链表.因此这个问题转换为两个单向链表的第一个公共结点(先分别遍历两个链表 ...

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

    P3379 [模板]最近公共祖先(LCA) 152通过 532提交 题目提供者HansBug 标签 难度普及+/提高 提交  讨论  题解 最新讨论 为什么还是超时.... 倍增怎么70!!题解好像有 ...

  5. Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)【转】【修改】

    一.基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成 ...

  6. 数据结构作业——sights(最短路/最近公共祖先)

    sights Description 美丽的小风姑娘打算去旅游散心,她走进了一座山,发现这座山有 n 个景点,由于山路难修,所以施工队只修了最少条的路,来保证 n 个景点联通,娇弱的小风姑娘不想走那么 ...

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

    The merchant Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 4556   Accepted: 1576 Desc ...

  8. [最近公共祖先] POJ 1330 Nearest Common Ancestors

    Nearest Common Ancestors Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 27316   Accept ...

  9. 图论--最近公共祖先问题(LCA)模板

    最近公共祖先问题(LCA)是求一颗树上的某两点距离他们最近的公共祖先节点,由于树的特性,树上两点之间路径是唯一的,所以对于很多处理关于树的路径问题的时候为了得知树两点的间的路径,LCA是几乎最有效的解 ...

随机推荐

  1. HDU(1572),最短路,DFS

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1572 很久没写深搜了,有点忘了. #include <iostream> #include ...

  2. 数据库查询优化器的艺术:原理解析与SQL性能优化

    数据库查询优化器的艺术 作者:李海翔 Oracle公司MySQL全球开发团队.资深专家 简单的浏览了一遍,由于以前没有接触过SQL优化这些知识,读起来还是非常吃力的,不过收获还是很大的. 作者通过对M ...

  3. 什么是“鸭子类型(duck typing)”?

    在计算机编程世界里会接触到一个知识点 —— duck typing,叫“鸭子类型”.   它有一个形象的解释: “当看到一只鸟走起来像鸭子.游泳起来像鸭子.叫起来也像鸭子,那么这只鸟就可以被称为鸭子. ...

  4. flash视频器播放器代码

    <html> <head> <meta http-equiv="Content-Type" content="text/html; char ...

  5. 一维条码打印的C#实现(Code128)

    1.CODE128基础知识 CODE128有三个版本: CODE128A: 标准数字和字母, 控制符, 特殊字符 CODE128B: 标准数字和字母, 小写字母, 特殊字符 CODE128C: [00 ...

  6. Cheatsheet: 2014 07.01 ~ 07.31

    Web Maximize Compression with Zopfli Browser Detection with JavaScript Simple MySQL Master HA with m ...

  7. sql语句中的注释符号

    sql语句中的注释符号     mysql # 到该行结束.-- 到该行结束 以及 的注释方格:mysql> SELECT 1+1;     # 这个注释直到该行结束mysql> SELE ...

  8. Log4j2在WEB项目中配置

    最近决定在新WEB项目中使用新的日志系统Log4j2. 官方介绍和学习文档网址为http://logging.apache.org/log4j/2.x/ 首先在WEB项目中引入以下几个jar包: ① ...

  9. 清除SVN获取文件的所有.svn文件夹

    Windows Registry Editor Version 5.00[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\清除SVN信息] @=&qu ...

  10. 基本的DMA控制器

    DMA的基本概念 直接内存访问(DMA)是一种完全由硬件执行I/O交换的工作方式.在这种方式中,DMA控制器从CPU完全接管对总线的控制,数据交换不经过CPU,而直接在内存和I/O设备之间进行 .DM ...