zoukankan      html  css  js  c++  java
  • UVa OJ 115 Climbing Trees (家谱树)

    Time limit: 3.000 seconds
    限时:3.000秒

    Background
    背景

    Expression trees, B and B* trees, red-black trees, quad trees, PQ trees; trees play a significant role in many domains of computer science. Sometimes the name of a problem may indicate that trees are used when they are not, as in the Artificial Intelligence planning problem traditionally called the Monkey and Bananas problem. Sometimes trees may be used in a problem whose name gives no indication that trees are involved, as in the Huffman code.
    表达式树、B/B*树、红黑树、四叉树、PQ树……,树在计算机科学的许多领域中都扮演了重要的角色。有些问题的名称表明其使用了树结构,比如智能规划问题在传统上称作“猴子与香蕉”问题。有些问题确实用到了树的结构,但在它的名称中并没有表示出来,比如哈夫曼编码。

    This problem involves determining how pairs of people who may be part of a "family tree" are related.
    本问题是要确定两个人是否在一个家谱中存在亲属关系。

    The Problem
    问题

    Given a sequence of child-parent pairs, where a pair consists of the child's name followed by the (single) parent's name, and a list of query pairs also expressed as two names, you are to write a program to determine whether the query pairs are related. If the names comprising a query pair are related the program should determine what the relationship is. Consider academic advisees and advisors as exemplars of such a single parent genealogy (we assume a single advisor, i.e., no co-advisors).
    给定一系列的“子-父”姓名对作为家谱,每对姓名中前者为子,后者为父(单亲)。再给定一系列的待查姓名对,表示两个人的姓名。你要写一个程序来确定待查 对中的两个姓名是否在家谱中是否存在亲属关系。如果确定待查对中的两个姓名存在亲属关系,则需输出是何种关系。大学里学生和导师的关系就可以视为单亲血统的例子(我们假设每个学生只有一个导师,没有共同指导的情况)。

    In this problem the child-parent pair p q denotes that p is the child of q. In determining relationships between names we use the following definitions:
    在这个问题中,“子-父”对p q表示p是q的子。在确定姓名关系的过程中,我们有以下的归纳定义:

    • p is a 0-descendent of q (respectively 0-ancestor) if and only if the child-parent pair p q (respectively q p ) appears in the input sequence of child-parent pairs.
      p是q的第0个后代,当且仅当输入序列中存在一个“子-父”对p q,指出了p和q为“子-父”关系。
    • p is a k-descendent of q (respectively k-ancestor) if and only if the child-parent pair p r (respectively q r ) appears in the input sequence and r is a (k - 1) - descendent of q (respectively p is a (k-1)-ancestor of r).
      p是q的第k个后代,当且仅当r是q的(k - 1)个后代,并且输入序列中存在一个“子-父”对p r,指出了p和r为“子-父”关系。

    For the purposes of this problem the relationship between a person p and a person q is expressed as exactly one of the following four relations:
    就此问题而言,p和q两人之间的亲属关系只能是以下4种关系中的一种:

    1. child -- grand child, great grand child, great great grand child, etc.
      By definition p is the "child" of q if and only if the pair p q appears in the input sequence of child-parent pairs (i.e., p is a 0-descendent of q); p is the "grand child" of q if and only if p is a 1-descendent of q; and
      子关系——包括孙“grand child”、曾孙“great grand child”、玄孙“great great grand child”等。
      根据定义,p是q的“child”(子),当且仅当输入序列中存在一个“子-父”对p q,指出了它们为“子-父”关系(即p是q的第0个后代);p是q的“grand child”(孙)当且仅当p是q的第1个后代;且:

      fom1
      if and only if p is an (n + 1)-descendent of q.
      当且仅当p是q的第(n + 1)个后代。
    2. parent -- grand parent, great grand parent, great great grand parent, etc.
      父关系——包括祖父“grand parent”、曾祖父“great grand parent”、高祖父“great great grand parent”等。 
      By definition p is the "parent" of q if and only if the pair q p appears in the input sequence of child-parent pairs (i.e., p is a 0-ancestor of q); p is the "grand parent" of q if and only if p is a 1-ancestor of q; and
      父关系的定义与子关系对应,参见上面的“子关系”。
      fom2
      if and only if p is an (n+1)-ancestor of q.
    3. cousin -- 0th cousin, 1st cousin, 2nd cousin, etc.; cousins may be once removed, twice removed, three times removed, etc.
      堂亲——第0堂亲“0th cousin”、第1堂亲“1st cousin”、第2堂亲等“2nd cousin”。堂亲存在隔代关系,隔1代、隔2代、隔3代等。
      (译注:美国的堂亲关系与中国的相差很大,“nth cousin”中的n是指二人中辈份较长的一人与最近共同祖先相差的辈数减1。比如你和姑妈(父亲的亲姐妹)的最近 共同祖先是祖父,关系就是0th cousin;你和堂弟(姑妈的儿子)的最近共同祖先也是祖父,关系则是1st cousin;你和姨奶(奶奶的亲姐妹)儿子的关系是2nd cousin。两个cousin关系的人相差的辈数m,用“cousin removed m”来表示。)

      By definition p and q are "cousins" if and only if they are related (i.e., there is a path from p to q in the implicit undirected parent-child tree). Let r represent the least common ancestor of p and q (i.e., no descendent of r is an ancestor of both p and q), where p is an m-descendent of r and q is an n-descendent of r.
      根据定义,p和q为堂亲“cousins”,当且仅当他们存在亲属关系(即在家谱树中隐含着一条不论方向的由p至q的路径)。令r表示p和q的最近共同祖先(即r没有后代也是p和q的共同祖先),p是r的第m个后代,q是r的第n个后代。
      Then, by definition, cousins p and q are "kth cousins" if and only if k = min(n, m), and, also by definition, p and q are "cousins removed j times" if and only if j = |n - m|.
      那么根据定义,堂亲p和q是“kth cousin”当且仅当k = min(n, m)。p和q是“cousin removed j times”当且仅当j = |n - m|。
    4. sibling -- 0th cousins removed 0 times are "siblings" (they have the same parent).
      同胞——“0th cousins removed 0 times”为同胞“siblings”(他们有共同的父)。

    The Input
    输入

    The input consists of parent-child pairs of names, one pair per line. Each name in a pair consists of lower-case alphabetic characters or periods (used to separate first and last names, for example). Child names are separated from parent names by one or more spaces. Parent-child pairs are terminated by a pair whose first component is the string "no.child". Such a pair is NOT to be considered as a parent-child pair, but only as a delimiter to separate the parent-child pairs from the query pairs. There will be no circular relationships, i.e., no name p can be both an ancestor and a descendent of the same name q.
    输入由一系列的名字对构成,每对独占一行。各对中的名字都由小写字母和点号(比如可用来分隔姓和名)构成。子和父之间由1个或多个空格隔开。当输入的姓名对前者的名字为“no.child”时,表示“子-父”对输入结束。结束符仅仅用于分隔“子-父”对和待查对,程序不能将其作为一个“子-父”对来处理。输入中不会存在循环关系,即任何名字p都不可能同为q的后代和祖先。

    The parent-child pairs are followed by a sequence of query pairs in the same format as the parent-child pairs, i.e., each name in a query pair is a sequence of lower-case alphabetic characters and periods, and names are separated by one or more spaces. Query pairs are terminated by end-of-file.
    “子-父”对下面是待查对,格式与“子-父”对相同,即每个待查对中的名字都由小写字母和点号构成,两个名字间由1个或多个空格隔开。待查对的输入由EOF结束。

    There will be a maximum of 300 different names overall (parent-child and query pairs). All names will be fewer than 31 characters in length. There will be no more than 100 query pairs.
    总共最多出现300个不同的名字(包括“子-父”对和待查对)。所有姓名都少于31个字符长度。最多100个待查对。

    The Output
    输出

    For each query-pair p q of names the output should indicate the relationship p is-the-relative-of q by the appropriate string of the form
    对于每个姓名待查对,都要以下列的输出形式表示p与q的关系

    • child, grand child, great grand child, great great ...great grand child
    • parent, grand parent, great grand parent, great great ...great grand parent
    • sibling
    • n cousin removed m
    • no relation

    If an m-cousin is removed 0 times then only m cousin should be printed, i.e., removed 0 should NOT be printed. Do not print st, nd, rd, th after the numbers.
    如果一个“m cousin”相隔0代,那么只需打印出“m cousin”,也就是说输出中不能出现“removed 0”。不要在任何数字后面添加“st”、“nd”、“rd”、“th”等后缀。

    Sample Input
    输入示例

    alonzo.church oswald.veblen
    stephen.kleene alonzo.church
    dana.scott alonzo.church
    martin.davis alonzo.church
    pat.fischer hartley.rogers
    mike.paterson david.park
    dennis.ritchie pat.fischer
    hartley.rogers alonzo.church
    les.valiant mike.paterson
    bob.constable stephen.kleene
    david.park hartley.rogers
    no.child no.parent
    stephen.kleene bob.constable
    hartley.rogers stephen.kleene
    les.valiant alonzo.church
    les.valiant dennis.ritchie
    dennis.ritchie les.valiant
    pat.fischer michael.rabin

    Sample Output
    输出示例

    parent
    sibling
    great great grand child
    1 cousin removed 1
    1 cousin removed 1
    no relation

    Analysis
    分析

    这是一道典型的关于LCA(最近公共祖先)算法的题目。跟据这道题的特点,将LCA问题转换为RQM问题是很自然的做法。关于转换的算法和RQM问题的高效算法我将在另外的文章中给出。

    按照题目的要求,一个子只能存在一个父(we assume a single advisor, i.e., no co-advisors),因此这道题的解法其实可以非常多。然而非常操蛋的是评判所用数据中确实出现了一子多父的情况。如果你忽略了这样的非法输入,将会得到WA。程序必须能够“正确”的处理这样的异常,但是处理的方法文中并没有给出。

    事实上的处理方式是没有规律的,让未考虑多父情况的转换RQM算法强制运行在这种异常的输入情况下,得到的结果就是所谓的“正确”结果。如果你一不小心选择了与出题人思路不同的算法,那么无论你将程序写的多么万无一失,无论你如何处理多父的情况,你也永远只能得到WA。

    我在这道题上卡了相当长的时间,原因就是我的高效算法不能满足出题人的变态要求。极度反感这道题!血泪教训:必须使用将LCA问题转换为RMQ问题的算法,其它算法一概WA

    Solution
    解答

    #include <algorithm>
    #include <iostream>
    #include <map>
    #include <vector>
    #include <string>
    using namespace std;
    struct NODE {int nPar; int nPos; vector<int> Chi;};
    vector<NODE> Tree;
    //分别用于存储RQM的遍例次序和深度
    vector<int> Order, Depth;
    //由树建立RQM表,递归方式
    void BuildRMQTable(int nNode, int nDepth) {
    	//进入当前节点,存储其编号和深度
    	Order.push_back(nNode);
    	Depth.push_back(nDepth);
    	NODE &Node = Tree[nNode];
    	//第一次遍例该节点,记录节点在表中的位置
    	Node.nPos = Node.nPos == -1 ? (Order.size() - 1) : Node.nPos;
    	//多叉树的标准深度遍例方式,依次访问所有子节点
    	for (vector<int>::iterator i = Node.Chi.begin(); i != Node.Chi.end();) {
    		BuildRMQTable(*i++, nDepth + 1);
    		//回到当前节点,存储其编号和深度
    		Order.push_back(nNode);
    		Depth.push_back(nDepth);
    	}
    }
    //主函数
    int main(void) {
    	//姓名Hash
    	map<string, int> NameTbl;
    	//新节点的初始值,父为-1,RMQ表位置为-1
    	NODE NewNode = {-1, -1};
    	//循环输入所有的姓名,str1为子,str2为父
    	for (string str1, str2; cin >> str1 >> str2 && str1 != "no.child";) {
    		//不存在给定名称,则加入该名称
    		if (NameTbl.end() == NameTbl.find(str2)) {
    			NameTbl[str2] = Tree.size();
    			Tree.push_back(NewNode);
    		}
    		if (NameTbl.end() == NameTbl.find(str1)) {
    			NameTbl[str1] = Tree.size();
    			Tree.push_back(NewNode);
    		}
    		//建立父子关系
    		Tree[NameTbl[str2]].Chi.push_back(NameTbl[str1]);
    		Tree[NameTbl[str1]].nPar = NameTbl[str2];
    	}
    	//为避免出现"多树"的情况,建立虚拟的总根,放在列表最后
    	Tree.push_back(NewNode);
    	//遍例所有节点
    	for (vector<NODE>::iterator i = Tree.begin(); i != Tree.end() - 1; ++i ) {
    		//找出没有父的节点,即父为-1的节点
    		if (i->nPar == -1) {
    			//令父其父节点为总根
    			i->nPar = Tree.size() - 1;
    			//在总根的子节点列表中加入该节点
    			Tree.back().Chi.push_back(i - Tree.begin());
    		}
    	}
    	//从总根开始递归建立RMQ表
    	BuildRMQTable(Tree.size() - 1, 0);
    	//循环输入每一组查询
    	for (string str1, str2; cin >> str1 >> str2;) {
    		map<string, int>::iterator i1 = NameTbl.find(str1);
    		map<string, int>::iterator i2 = NameTbl.find(str2);
    		//如果两个名子中有任一个没有记录,认为无关
    		if (i1 == NameTbl.end() || i2 == NameTbl.end()) {
    			cout << "no relation" << endl;
    			continue;
    		}
    		//得到两个名子在查询表中的位置
    		int n1 = Tree[i1->second].nPos, n2 = Tree[i2->second].nPos;
    		//保持位置较小者在前
    		if (Depth[n1] > Depth[n2]) {
    			swap(n1, n2);
    		}
    		//RQM查询
    		vector<int>::iterator iAnc = min_element(
    			Depth.begin() + min(n1, n2), Depth.begin() + max(n1, n2) + 1);
    		//如果小最共同祖先(LCS)为总根,认为无关
    		if (Tree[Order[iAnc - Depth.begin()]].nPar == -1) {
    			cout << "no relation" << endl;
    			continue;
    		}
    		//nRemoved为隔代数,nCousin为二者与LCS距离的最小值
    		int nRemoved = Depth[n2] - Depth[n1];
    		int nCousin = Depth[n1] - *iAnc;
    		//二者中有一人为LCS的情况
    		if(nCousin == 0) {
    			for (; nRemoved > 2; --nRemoved) {
    				cout << "great ";
    			}
    			if (nRemoved > 1) {
    				cout << "grand ";
    			}
    			cout << (Tree[i1->second].nPos == n1 ? "parent" : "child") << endl;
    		}
    		//LCS为二者生父
    		else if (nCousin == 1 && nRemoved == 0) {
    			cout << "sibling" << endl;
    		}
    		//堂亲
    		else {
    			cout << nCousin - 1 << " cousin";
    			if (nRemoved > 0) {
    				cout << " removed " << nRemoved;
    			}
    			cout << endl;
    		}
    	}
    	return 0;
    }
    



    知识共享许可协议 作者:王雨濛;新浪微博:@吉祥村码农;来源:《程序控》博客 -- http://www.cnblogs.com/devymex/
    此文章版权归作者所有(有特别声明的除外),转载必须注明作者及来源。您不能用于商业目的也不能修改原文内容。
  • 相关阅读:
    html 的一些基础操作
    java 通过反射调用属性,方法,构造器
    java 通过反射获取类属性结构,类方法,类父类及其泛型,类,接口和包
    java 反射,类的加载过程以及Classloader类加载器
    java 随机读写访问流及seek方法
    java 序列化机制
    java 标准输入输出流,打印流,数据流
    hp400 硒鼓加粉图解
    Delphi XE5 android 获取网络状态
    Delphi XE5 常见问题解答
  • 原文地址:https://www.cnblogs.com/devymex/p/1798351.html
Copyright © 2011-2022 走看看