首页 > ACM题库 > UVA > UVa-148-Anagram checker(回文构词检测)[搜索]
2014
01-28

UVa-148-Anagram checker(回文构词检测)[搜索]

Time limit: 3.000 seconds
限时:3.000秒

 

Problem
问题

It is often fun to see if rearranging the letters of a name gives an amusing anagram. For example, the letters of ‘WILLIAM SHAKESPEARE’ rearrange to form ‘SPEAK REALISM AWHILE’.
经常会发现将一些短语中的字母重新排列后又形成了有趣的新短语。比如“WILLIAM SHAKESPEARE”可以重新排列为“SPEAK REALISM AWHILE”。(译注,前者是“威廉莎士比亚”,后者为“说段现实主义”)

 

Write a program that will read in a dictionary and a list of phrases and determine which words from the dictionary, if any, form anagrams of the given phrases. Your program must find all sets of words in the dictionary which can be formed from the letters in each phrase. Do not include the set consisting of the original words. If no anagram is present, do not write anything, not even a blank line.
写一个程序,读入字典和一些短语,并在字典中找出哪些单词可以通过重新排列和组合构成给定的短语。对于每一个短语,字典中可能存在多个可以重组为该短语的单词集合,你的程序要把所有这些单词集合都找出来。但不要包括与短语中已有的单词全部重合的单词集合。如果不存在可以重组为给定短语的单词集合,则无需输出任何结果,包括空行。

 

Input
输入

Input will consist of two parts. The first part is the dictionary, the second part is the set of phrases for which you need to find anagrams. Each part of the file will be terminated by a line consisting of a single #. The dictionary will be in alphabetic order and will contain up to 2000 words, one word per line. The entire file will be in upper case, and no dictionary word or phrase will contain more than 20 letters. You cannot assume the language being used is English.
输入包括两部分。第一部分是字典,第二部分是一些短语,作为你要查找的对象。数据的各部分均由独占一行的单个#号表示结束。字典将按字母表顺序给出,最多包括2000个单词,每个单词独占一行。输入的数据全部由大写字母构成,所有字典单词和短语都最多包含20个字母。你不要以为输入的都是英语。

 

Output
输出

Output will consist of a series of lines. Each line will consist of the original phrase, a space, an equal sign (=), another space, and the list of words that together make up an anagram of the original phrase, separated by exactly one space. These words must appear in alphabetic sequence.
输出由多行组成,每行包括原始短语,一个空格,一个等于号(=),再一个空格,后面是可以重组为该短语的单词列表,两个单词间由一个空格隔开。这些单词都要按照字母顺序输出。

 

Sample input
输入示例

ABC
AND
DEF
DXZ
K
KX
LJSRT
LT
PT
PTYYWQ
Y
YWJSRQ
ZD
ZZXY
#
ZZXY ABC DEF
SXZYTWQP KLJ YRTD
ZZXY YWJSRQ PTYYWQ ZZXY
#

 

Sample output
输出示例

SXZYTWQP KLJ YRTD = DXZ K LJSRT PTYYWQ
SXZYTWQP KLJ YRTD = DXZ K LT PT Y YWJSRQ
SXZYTWQP KLJ YRTD = KX LJSRT PTYYWQ ZD
SXZYTWQP KLJ YRTD = KX LT PT Y YWJSRQ ZD

 

Analysis
分析

貌似除了递归式暴力搜索外,没有其它的有效算法了。这样的话,对代码性能的要求就比较高了,为了快速的判断一个短语是否包含某个单词,必须找出一种特定的数据结构来表示。

我的方法是给每个单词附加一个26字节长度的数组,各个元素分别代表a, b, …, z这26个字母在单词中出现的次数,把这个数组称为字符串的特征(简称特征)。对于短语也同样处理(忽略中间的空格),如果短语的特征均大于或等于某个单词的特征,即可判定该短语包含该单词。如果要在短语中删除或添加某个单词,只需对特征进行加减即可。

递归的过程是:从字典的第一个单词开始查找,找到第一个能被短语包含的单词,从短语特征中减掉这个单词并将该单词加入结果列表;然后将字典的起点移动到这个单词之后,开始下一级递归调用。如果下一级返回,则重新恢复原短语特征(再加上前面的单词)并从结果集中移除该单词,继续向字典后面查找。如果在某一级调用的开始发现短语特征已经减为0,则说明已经找到可以重组为该短语的单词集,按要求输出结果即可(注意排除单词集与短语中的单词列表完全重合的情况)。

在具体实现中,我将结果集直接生成为字符串是为了代码比较清晰,如果要追求效率应另用链表或双端队列来保存,以加快添加和删除的速度。

 

Solution
分析

#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
struct WORD {
	string str; char Trait[26];
	bool operator>=(const WORD &Other) { //比较两个单词的特征,确定是否包含
		int i = 0;
		for (; i < 26 && Trait[i] >= Other.Trait[i]; ++i);
		return (i == 26); //全部特征都大于或等于时,判定存在包含关系
	}
	WORD& operator-=(const WORD &Other) { //从前一个单词中减掉后面的单词
		for (int i = -1; ++i < 26; Trait[i] -= Other.Trait[i]); //特征操作
		return *this;
	}
	WORD& operator+=(const WORD &Other) { //在前面一个单词中加上后面的单词
		for (int i = -1; ++i < 26; Trait[i] += Other.Trait[i]); //特征操作
		return *this;
	}
};
void GenTrait(const char *Str, char *Trait) { //根据字符串生成特征
	for (fill(&Trait[0], &Trait[26], 0); *Str != 0; ++Trait[*Str++ - 'A']);
}
typedef vector<WORD>::const_iterator DICTITER;
//递归搜索,iBeg和iEnd为搜索字典段的起点和终点。字典必须有序
void SearchAnagram(DICTITER iBeg, DICTITER iEnd, WORD &Phrase) {
	int nZeroCnt = 0;
	string &str = Phrase.str;
	//判断短语中所有字母是否都以在字典中找到
	for (; nZeroCnt < 26 && Phrase.Trait[nZeroCnt] == 0; ++nZeroCnt);
	if (nZeroCnt == 26) { //所有字母都找到,输出结果
		istringstream ss(str); //判定找到的单词集是否和原单词集相同
		vector<string> SrcWords, GenWords;
		//从输出字符串中提取原短语中的单词列表和查找到的单词集
		for (string Word; ss >> Word && Word != "="; SrcWords.push_back(Word));
		for (string Word; ss >> Word; GenWords.push_back(Word));
		//对原短语中的单词列表排序。查找到的单词集已有序
		sort(SrcWords.begin(), SrcWords.end());
		if (SrcWords != GenWords) { //判定是否相同
			cout << str << endl; //不同时输出结果
		}
		return; //返回上一级调用
	}
	//从指定的开始到结束,查找字典段中是否有单词被短语包含
	for (DICTITER i = iBeg; i != iEnd; ++i) {
		if (Phrase >= *i) { //根据特征进行查找
			Phrase -= *i; //将该单词的特征从短语特征中减掉
			str.push_back(' '); //结果字符串的最后加上该短语
			str.append(i->str);
			SearchAnagram(i + 1, iEnd, Phrase); //进入下一级查找过程
			str.erase(str.length() - i->str.length() - 1); //恢复结果字符串
			Phrase += *i; //恢复短语特征
		}
	}
}
//主函数
int main(void) {
	vector<WORD> Dict, Subset;
	//读入所有字典,并生成每个单词的特征
	for (WORD Word; cin >> Word.str && Word.str != "#";) {
		GenTrait(Word.str.c_str(), Word.Trait);
		Dict.push_back(Word);
	}
	//读入所有的短语,逐个处理
	for (string Line; getline(cin, Line) && Line != "#"; Subset.clear()) {
		WORD Phrase = {Line}; //保留原短语字符串
		//删除指定短语中的空格(准备生成特征)
		Line.erase(remove(Line.begin(), Line.end(), ' '), Line.end());
		if (Line.empty()) {
			continue;
		}
		GenTrait(Line.c_str(), Phrase.Trait); //生成特征
		//在字典中筛选中被原短语包含的单词,用作查找
		for (vector<WORD>::iterator i = Dict.begin(); i != Dict.end(); ++i) {
			if (Phrase >= *i) { //根据特定判断是否被包含
				Subset.push_back(*i);
			}
		}
		Phrase.str.append(" ="); //按照格式,在原短语后加上空格和=号
		SearchAnagram(Subset.begin(), Subset.end(), Phrase); //递归查找过程
	}
	return 0;
}

  1. 您没有考虑 树的根节点是负数的情况, 若树的根节点是个很大的负数,那么就要考虑过不过另外一边子树了

  2. 漂亮。佩服。
    P.S. unsigned 应该去掉。换行符是n 不是/n
    还可以稍微优化一下,
    int main() {
    int m,n,ai,aj,bi,bj,ak,bk;
    while (scanf("%d%d",&m,&n)!=EOF) {
    ai = sqrt(m-1);
    bi = sqrt(n-1);
    aj = (m-ai*ai-1)>>1;
    bj = (n-bi*bi-1)>>1;
    ak = ((ai+1)*(ai+1)-m)>>1;
    bk = ((bi+1)*(bi+1)-n)>>1;
    printf("%dn",abs(ai-bi)+abs(aj-bj)+abs(ak-bk));
    }
    }

  3. 换句话说,A[k/2-1]不可能大于两数组合并之后的第k小值,所以我们可以将其抛弃。
    应该是,不可能小于合并后的第K小值吧

  4. 第一句可以忽略不计了吧。从第二句开始分析,说明这个花色下的所有牌都会在其它里面出现,那么还剩下♠️和♦️。第三句,可以排除2和7,因为在两种花色里有。现在是第四句,因为♠️还剩下多个,只有是♦️B才能知道答案。