• 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;
    }
    
  • 相关阅读:
    切换某个窗口为当前窗口并显示在最前面---非置顶
    C语言算法-求两直线夹角计算公式
    Qt编译时MinGW去掉对gcc动态库的依赖
    解决不能从 WTL::CString 转换为 ATL::CSimpleString & 的问题
    gcc编译器对宽字符的识别
    4月10日学习笔记——jQuery选择器
    HTML5
    4月8日学习笔记(js基础)
    网站易用性2
    网站易用性
  • 原文地址:https://www.cnblogs.com/gtarcoder/p/5540116.html
Copyright © 2020-2023  润新知