程序控

IPPP (Institute of Penniless Peasent-Programmer) Fellow

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: :: 管理 ::
  77 随笔 :: 0 文章 :: 442 评论 :: 0 引用

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;
}
posted on 2010-08-12 21:32  Devymex  阅读(...)  评论(...编辑  收藏