虽然这似乎是Crypt Kicker Problem的副本,但事实并非如此。
我已经解决了这个问题,但我并不是所有人都满意我的解决方案。问题陈述是:
加密文本的一种常见但不安全的方法是置换字母表中的字母。换句话说,字母表中的每个字母在文本中始终被其他字母替换。为了确保加密是可逆的,没有两个字母被相同的字母替换。
您的任务是解密几个编码的文本行,假设每行使用不同的替换集,并且解密文本中的所有单词都来自已知单词的字典。
输入
输入包含一个包含整数n的行,后跟n个小写单词,每行一个,按字母顺序排列。这n个单词组成可能出现在解密文本中的单词字典。字典后面有几行输入。每条线都按上述方式加密。
字典中的字数不超过1,000个。没有字超过16个字母。加密行仅包含小写字母和空格,长度不超过80个字符。
输出
解密每一行并将其打印到标准输出。如果有多种解决方案,任何人都可以。如果没有解决方案,请用星号替换字母表中的每个字母。
示例输入
6
和
迪克
jane
粉扑
现货
yertle
bjvg xsb hxsn xsb qymm xsb rqat xsb pnetfn
xxxx yyy zzzz www yyyy aaa bbbb ccc dddddd
示例输出
dick and jane and puff and spot and yertle
**** *** **** *** **** *** **** *** ******
我粗暴地强迫了这个问题:我根据长度将字典分成了一个集合。然后我做了一个递归蛮力,我尝试了每个可能的替换基于字长,如果没有匹配则回溯。它有效,但我对解决方案非常不满意。我可能只是在迷恋,但似乎应该有一种更优雅的方式来解决问题。我的代码如下:
#include<iostream>
#include<algorithm>
#include<vector>
#include<sstream>
#include<string>
#include<map>
#include<set>
using namespace std;
bool Find(vector<set<string > > &dict,vector<string> &line, map<char,char> &dec,int spot){
//Check that the end of the line hasn't been reached
if(spot<line.size()){
//Get the size of the current word
int sSize=line[spot].size();
string cand;
cand.resize(sSize,'A');
//Attempt to decode the current word
for(int i=0;i<sSize;i++){
if(dec.find(line[spot][i])!=dec.end())
cand[i]=dec[line[spot][i]];
}
//Check all strings in the dictionary of the current length
for(set<string>::iterator it=dict[sSize].begin();it!=dict[sSize].end();it++){
bool notMatch=false;
for(int i=0;i<sSize;i++)
//A is used to signify an undecoded character, this if says if the character was
// decoded and it does not equal to corresponding character in the word, it's not a match
if(cand[i]!='A'&&cand[i]!=(*it)[i])
notMatch=true;
if(notMatch)
continue;
for(int i=0;i<sSize;i++)
//if it is a feasible match, then add the learned characters to the decoder
if(cand[i]=='A')
dec.insert(pair<char,char> (line[spot][i],(*it)[i]));
//Keep decoding
if(Find(dict,line,dec,spot+1))
return true;
//If decoding failed, then remove added characters
for(int i=0;i<sSize;i++)
if(cand[i]=='A')
dec.erase(line[spot][i]);
}
if(spot==0){
//This means no solution was found, fill decoder with a map to astericks
string b="qwertyuiopasdfghjklzxcvbnm";
for(int i=0;i<b.size();i++)
dec.insert(pair<char,char> (b[i],'*'));
}
return false;
}
return true;
}
int main(){
int size;
cin >> size;
vector<set<string> > dict;
dict.resize(17);
string grab;
for(int i=0;i<size;i++){
//Bucket dictionary
cin >> grab;
dict[grab.size()].insert(grab);
}
while(getline(cin,grab)){
stringstream in(stringstream::in |stringstream::out);
in << grab;
vector<string> line;
while(in >> grab)
line.push_back(grab);
map<char,char> dec;
Find(dict,line,dec,0);
for(int i=0;i<line.size();i++){
for(int j=0;j<line[i].size();j++)
cout << dec[line[i][j]];
if(i!=line.size()-1)
cout << " ";
else
cout << endl;
}
}
}
另外,我对那些在c ++中不起作用的解决方案并不特别感兴趣。仅仅因为它是我在编程竞赛中使用的语言,所以我仅限于解决这些问题。我也知道有很多风格和次要的效率,我可以采取不同的方式,而不是太多关注我,我错过了一两次休息。主要是我只是想知道是否有一个更简单的解决方案,或者我的实现是否过于复杂。感谢。
答案 0 :(得分:4)
我会通过比较单词中的字母模式来解决这个问题。首先,我会像这样转换字典:
and -> 123
dick -> 1234
jane -> 1234
puff -> 1233
spot -> 1234
yertle -> 123452
这个特殊的字典不能很好地工作,但一般的想法是绘制出由字母组成的图案。例如,单词“letters”映射到1233245,这是一个更好的例子,因为有多个e和t。
然后我会对加密文本做同样的事情:
bjvg xsb hxsn xsb qymm xsb rqat xsb pnetfn -> 1234 123 1234 123 1233 123 1234 123 123452
我们可以进行反向查找并确定第二个单词是“和”,第五个单词是“puff”,第九个单词是“yertle”。 “dick”,“jane”和“spot”都有相同的模式,所以我们不能立即将它们分开,但是使用从“and”,“puff”和“yertle”获得的信息,你可以填写剩下的部分。
答案 1 :(得分:0)
这显然是一个回溯并解决问题。需要进行一定程度的系统猜测和验证。使用递归方法的回溯解决方案可以在这里找到: