hiho一下 第十五周
来源:互联网 发布:女装淘宝首页 编辑:程序博客网 时间:2024/05/29 09:14
- 题目
- 输入
- 解法
这道题目编码测试TLM(运行超时)。
题目
现在知道了N对父子关系——父亲和儿子的名字,并且这N对父子关系中涉及的所有人都拥有一个共同的祖先(这个祖先出现在这N对父子关系中)。现在有若干次提问——每次提问为两个人的名字(这两个人的名字在之前的父子关系中出现过),找出这两个人的所有共同祖先中辈分最低的一个是谁?
输入
输入
每个测试点(输入文件)有且仅有一组测试数据。
每组测试数据的第1行为一个整数N,意义如前文所述。
每组测试数据的第2~N+1行,每行分别描述一对父子关系,其中第i+1行为两个由大小写字母组成的字符串Father_i, Son_i,分别表示父亲的名字和儿子的名字。
每组测试数据的第N+2行为一个整数M,表示小Hi总共询问的次数。
每组测试数据的第N+3~N+M+2行,每行分别描述一个询问,其中第N+i+2行为两个由大小写字母组成的字符串Name1_i, Name2_i,分别表示小Hi询问中的两个名字。
对于100%的数据,满足N<=10^5,M<=10^5, 且数据中所有涉及的人物中不存在两个名字相同的人(即姓名唯一的确定了一个人),所有询问中出现过的名字均在之前所描述的N对父子关系中出现过,第一个出现的名字所确定的人是其他所有人的公共祖先。
输出
对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,表示查询的结果:他们的所有共同祖先中辈分最低的一个人的名字。
样例输入
4Adam SamSam JoeySam MichealAdam Kevin3Sam SamAdam SamMicheal Kevin
样例输出
SamAdamAdam
解法
(照抄hihoCoder上面的解法)
“唔……我感觉你这个问题和上次提出的问题没有什么太大区别嘛,还简化了许多……那我就用上次的算法不行么?”小Ho疑问道。
“NoNoNo,仔细注意一下数据范围,这可不是上次的算法就能够解决的~”小Hi道。
“是诶,上次无论是人数还是询问数,都远远少于这一次的……那么我该如何进行计算呢?”小Ho不禁问道。
“我认为有这样两种法子,其中一种呢,就是将一段时间内的询问收集起来统一处理,利用一些与询问个数无关的算法来进行计算,这样时间复杂度在平摊到每个询问的时候就不会很高了。”小Hi缓缓道来。
“听起来很有道理。”
“就是很有道理好么!”小Hi道:“我们不妨再抽象这个问题,其实本质就是给你一棵树,然后每次询问是求这棵树种某两个结点的最近公共祖先——所有公共祖先中层数最深的。”
“是的!而且这个最近公共祖先怎么和之前那一次所说的转折点很类似啊?”小Ho似乎回忆起了什么往事。
“差不多一个意思把,重点是在于我接下来要说的——用于求解最近公共祖先问题的离线算法,为什么要叫做离线算法呢,因为我需要一次性收集若干个询问之后才能通过这个算法一同计算出这些询问的答案,而不能像一个在线系统一样对于每一个询问都即时的进行计算并给出答复。”小Hi解释道。
“你说了这么多,可我还是不知道这个算法是怎么回事呢!”小Ho抱怨道。
“别急,你看这个图——好吧,其实是棵树,代表着我们这个问题的一个输入,而这几个是我们要处理的询问。”小Hi在准备好的黑板上写写画画道。
“好的。”小Ho点点头。
“而我现在要做的呢,是以深度优先搜索的顺序来访问这棵树。”小Hi继续道:“在这个过程中,我会给这棵树的结点染色,一开始所有结点都是白色的。而当我第一次经过某个结点的时候,我会将它染成灰色,而当我第二次经过这个结点的时候——也就是离开这棵子树的时候,我会将它染成黑色。”
“但是这样做的意义何在呢?”小Ho的问题来了。
“举个例子,当我们深度优先搜索到A结点时,我们发现A结点和B结点是我们需要处理的一组询问。”小Hi在黑板上画着。
小Ho点了点头。
“这个时候,这个图的染色情况是这样的。”小Hi顺手就涂上了颜色,继续道:“我们这个时候就要去查看B结点的颜色——灰色,说明我们进入了以B结点为根的子树,但是还没有从这棵子树中出去,你知道这意味着什么吗?”
“意味着A结点在B结点所在的子树中——那么B结点就是A和B结点的最近公共祖先了?”小Ho答道。
“没错,但这只是一种简单的情况,如果我询问的不是A和B这两个结点,而是A和C这两个结点,那么在访问到A的时候,C的颜色是黑色的,这时候我该怎么处理呢?”小Hi继续问道。
“唔……首先肯定只能从所有灰色结点中找——这是A结点的所有祖先结点!那么……就是C结点向上的第一个灰色结点?”小Ho答道。
“是的,但是你这样做不会和上一次的朴素做法一样低效率么?”小Hi反问道。
“的确是的……每次都要向上找会很吃不消的。”小Ho困惑了:“那么我该怎么办呢?”
“先别着急,我们把之前的分情况讨论结束,再来细谈这个问题……那么接下来只有一种可能了,如果询问的时A和P这两个结点,而此时P还是白色的,你觉得怎么处理比较合适?”小Hi道。
“唔……我觉得把,既然这个时候P还是白色,那么就先不要管这个询问了,毕竟现在关于P的信息一点都不知道。而且反正深度优先搜索会处理到P结点,那个时候A结点肯定已经不是白色了,就可以沿用之前的方法进行解决了!”小Ho沉思了一会,答道。
“是的!那么你有没有发现,这样一遍处理下来,你就可以求出所有准备好的询问的结果了——我先计算每个结点涉及到的询问,然后在深度优先搜索的过程中对结点染色,如果发现当前访问的结点是涉及到某个询问,那么我就看这个询问中另一个结点的颜色,如果是白色,则留待之后处理,如果是灰色,那么最近公共祖先必然就是这个灰色结点,如果是黑色,那么最近公共祖先就是这个黑色结点向上的第一个灰色结点。”小Hi总结道:“而我们唯一剩下的问题,就是怎么样快速的找到一个黑色结点向上的第一个灰色结点。”
小Ho若有所思的点了点头。
“还记得上周我和你讲的并查集问题么?”小Hi问道。
“还记得呢!但是那和这个问题又有什么关系呢?”小Ho问道。
“我们来这样想,当深度优先搜索进行到刚刚第二次经过C结点这一步的时候,是不是以C为根的子树中所有结点,它们向上找的第一个灰色结点都是C结点?”小Hi没有直接回答小Ho的问题,反倒提出了一个新的问题。
“是的。”
“那么如果我们将C这棵子树视为一个集合,而将C结点视为这个集合的代表元素的话,那么我在第二次经过C结点之后,将C结点染成黑色后,以C为根的子树中的所有结点的代表元素——或者说向上找的第一个灰色结点都变成了C的父亲结点——D结点?那么这一过程,是不是其实就是将C结点代表的集合合并到了D结点代表的集合中去了?”小Hi问道。
“唔……还真是,每个结点最开始都是一个独立的集合,每当一个结点由灰转黑的时候,就将它所在的集合合并到其父亲结点所在的集合中去。这样无论什么时候,任意一个黑色结点所在集合的代表元素就是这个结点向上的第一个灰色结点!也就是说,我只要在深度优先搜索的过程中维护这样的一些集合,我就能够对于每一个黑色结点,非常快捷的求出在当前的染色状况下,它向上的第一个灰色结点。”小Ho惊道:“这么一来,之前遗留的问题就得到了解决。”
“是的呢!而之所以你可以这样做的原因,这是因为这样的染色是不可逆的,对于一个黑色结点来说,它向上找的第一个灰色结点只会越来越高——这和集合的性质是相似的!”
“原来是这样!”小Ho感慨道。
“所以你接下来要做的事情,就是在深度优先搜索的过程中,不断的给结点染色,同时维护好这样的一些集合,并且对于询问一一做出回答呢!”小Hi笑道:“这可是有点难度哦~”
“没事!这对我来说,可是小菜一碟呢!”
编码测试:
#include<iostream>#include<vector>#include<map>#include<string>#include<stack>std::vector<std::vector<int> > V;//用来存储树关系std::map<std::string,int> Map;//用来表示名字和编号std::vector<std::string> Name;//用编号来找名字std::vector<int> Ancestor;//存储父子关系enum Color{White, Grey, Black};int FindQuestion(const std::vector<int>& Ques1,const std::vector<int>& Ques2,const std::vector<int>& Answer,const std::vector<Color>& C,int q,int start){ for(int i=start;i<Ques1.size();++i) { if(Answer[i]==-1&&(Ques1[i]==q||Ques2[i]==q)) { if(C[Ques1[i]]!=White&&C[Ques2[i]]!=White) return i; } } return -1;}int FindLowestAncestor(int n,const std::vector<Color>& C){ if(C[n]==Grey||Ancestor[n]==n) return n; Ancestor[n]=FindLowestAncestor(Ancestor[n],C);//顺便更新了最低祖先结点 return Ancestor[n];}/**ancesotr表示根节点M问题个数Ques1、Ques2存储了问题*/void DFS(int ancesotr,int M, std::vector<int>& Ques1, std::vector<int>& Ques2)//深度优先遍历{ std::vector<int> Answer(M,-1);//问题答案存储在里面 std::vector<Color> C(Name.size(),White);//全部初始化为白色 std::stack<int> Stack; Stack.push(ancesotr); C[ancesotr]=Grey; while(!Stack.empty())//深度优先遍历 { int top=Stack.top();//取出栈顶结点 int result=FindQuestion(Ques1,Ques2,Answer,C,top,0); while(result!=-1) { if(Ques1[result]==top) { if(C[Ques2[result]]==Grey)//祖先就是Ques2[result] { Answer[result]=Ques2[result]; } else { //找Ques2[result]的祖先的灰色结点 Answer[result]==FindLowestAncestor(Ques2[result],C); } } else if(Ques2[result]==top) { if(C[Ques1[result]]==Grey)//祖先就是Ques2[result] { Answer[result]=Ques1[result]; } else { //找Ques2[result]的祖先的灰色结点 Answer[result]=FindLowestAncestor(Ques1[result],C); } } result=FindQuestion(Ques1,Ques2,Answer,C,top,result);//再找一遍 } int i; for(i=0;i<V[top].size();++i) { if(C[V[top][i]]==White)//没被访问过 { Stack.push(V[top][i]); C[V[top][i]]=Grey; break; } } if(i==V[top].size()) { C[top]=Black; Stack.pop(); } } for(int i=0;i<Answer.size();++i) std::cout<<Name[Answer[i]]<<std::endl;}int main(){ int N; std::cin>>N; std::string Father, Son; int id=0;//给名字编号 for(int i=0; i<N; ++i) { std::cin>>Father>>Son; if(Map.count(Father)==0) { Map[Father]=id++;//给名字找ID Name.push_back(Father);//存储名字 std::vector<int> v; V.push_back(v); Ancestor.push_back(Map[Father]); } if(Map.count(Son)==0) { Map[Son]=id++; Name.push_back(Son); std::vector<int> v; V.push_back(v); Ancestor.push_back(Map[Son]); } //添加父子关系 V[Map[Father]].push_back(Map[Son]); Ancestor[Map[Son]]=Map[Father]; } int M; std::cin>>M; std::string name1, name2; std::vector<int> Ques1(M);//存储问题 std::vector<int> Ques2(M); for(int i=0; i<M; ++i) { std::cin>>name1>>name2; Ques1[i]=Map[name1]; Ques2[i]=Map[name2]; } int mostAncestor;//公共祖先 for(std::vector<int>::iterator iter=Ancestor.begin(); iter!=Ancestor.end();++iter) { if(*iter==iter-Ancestor.begin()) { mostAncestor=iter-Ancestor.begin(); break; } } DFS(mostAncestor,M,Ques1,Ques2); return 0;}
- hiho一下 第十五周
- hiho一下 第十五周 最近公共祖先·二 - 更新一下tarjan离线LCA模板
- hiho一下 第十五周——最近公共祖先·二(Trajan,离线LCA)
- hiho一下 第八周
- hiho一下 第二周
- hiho一下 第三周
- hiho一下 第五十周
- hiho一下58周
- hiho一下第六十周
- hiho一下 第六十周
- hiho一下 第143周 hiho密码
- hiho一下 第四十八周
- hiho一下 第四十九周
- hiho一下 第五十六周
- hiho一下 第五十八周
- hiho一下 第五十八周
- hiho一下 第五十九周
- hiho一下 第六十二周
- hdu2852 KiKi's K-Number 树状数组求第k大数
- windows下使用libxml2处理XML报文
- Hadoop 1.2.1升级2.6.0的一次崎岖之旅(包括Hive、HBase对应的升级)
- 蓝桥杯——安慰奶牛
- 通过不同的域名来访问根目录和根目录下的子目录
- hiho一下 第十五周
- 物联12:rfid与IC卡
- minicom配置及使用方法
- TCON控制字及TMOD寄存器
- HTTP请求(请求头、响应头)详解(一)
- Eclipse使用Maven创建Web时错误:Could not resolve archetype org.apache.maven.archetypes:maven-archetype-weba
- 准备篇:系统环境设置
- Yii2 缓存
- Android项目学习—Intent的作用详解