Description
NightElf 是一个古老的种族,在他们的遗迹上到处都有古NightElf 的奇妙文字,这些文字都是一些十分美妙而复杂的符号。然而,现在的NightElf 人早已不认识古文字了,因为古文字过于复杂,在几十个世纪前,他们就改用简单的英文字母来代替古代的文字符号,每个古文字符号对应一个英文字母。当然,正是因为对应关系渐渐被人遗忘、,所以古代文字已经无法阅读。除了这一点之外,古语同现代语没有任何区别。有一天,探险家Arthas 在一个古老的NightElf 遗迹前面被一扇门给拦住了。大门上用NightElf 文字刻着一句话。当地人告诉他,只有知道这句话的意思,解出一个谜题,才能进入这个遗迹。
于是 Arthas 下决心要找出古文字和现代的英文字母之间的联系,理解这句话的含义。然而这并不是一件轻松的工作。所辛,他的一个朋友是语言学家,告诉他一些很重要的事实:NightElf 文字共有n(1<=n<=27)个文字符号。其中n-1 个文字是有意义的,这n-1 个文字在现代语中用小写字母a,b,…中的前 n-1 个表示。还有一个文字是分隔符。
在句子中每个单词之间有且仅有一个分隔符,将单词隔开。另外NightElf 语言中的合法单词有着非常丰富的意义。因此只有很少的单词。当然,遗迹之门上的那一句话的每个单词都应该是NightElf 语言中的合法单词。于是Arthas想通过了解NightElf 语言中的所有单词,来试着找出句子的含义。
Input
第一行一个整数 t,表示有t 组测试数据。
每组数据第一行是两个整数n(1<=n<=27)和m(1<=m<=150),分别表示NightElf 语言的字符数和句子的字符数。
接着一行有 m 个整数,每个整数都在1 到n 之间,且1 到n 中的每一个数都会出现。每个数代表一个古NightElf 语言的文字符号,相同的数字表示相同的文字符号。
再接下来一个整数q(1<=q<=200),表示NightElf 语言的单词数。
接下来有 q 行,每行一个有现代语写出的(即用小写字母表示)单词。每个单词的长度不超过10。
Output
输出一行,是 Arthas 对输入古文的分析结果:
1)如果句子只有惟一的可能,将它翻译成现代语输出;
2)如果没有任何可能的单词组合组成句子,输出No solution;
3)如果句子有多种可能的组合,输出Cannot determine。
Sample Input
1
5 18
3 2 1 3 5 4 2 1 1 5 2 4 3 4 5 3 4 1
14
adca
bacb
cacc
adba
cabb
bdcc
dacc
baaa
dbab
acbc
daba
cba
abc
dcb
Sample Output
adca bdcc dbab abc
Hint
40%的数据满足: t=1,n<=6,m<=30,q<=40
100%的数据满足: t<=3,n<=27,m<=150,q<=200
【分析】
寻找集合{1,2,…}与集合{’a’,’b’,…}的一一对应关系,实质是一个排列生成问题。
Step1:确定分隔符(空格),需要穷举每个数字能否作为空格,原则是:一旦确定i是空格,则特殊的语言中分出来形成单词的长度要在单词表中出现才行。这可以排除显然无解的情况:例如样例中,以1作为空格,则第一个单词为3 2,而单词表中没有长度为2的单词。
Step2:搜索对应关系:
搜索策略1:用排列搜索出一种对应关系,再判断是否合法,时间复杂度O(n!)
搜索策略2:按照给出的特殊语言a[1]..a[m]的顺序进行搜索对应关系,这样的搜索顺序为我们剪枝提供了方便:
剪枝:搜索单词的时候,用一个数组建立对应关系,若发现当前字母与之前的对应关系有冲突,剪枝。
这个剪枝相当强,只需这个剪枝便可以100ms左右过了。
【代码】
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
#include<iostream>
#include<algorithm>
using namespace std;
int T,N,M,Nt,ANS,v[300],Q;
string w[300],s;
char match[30],path[30]; //path数组记录答案
bool f[30];
int ans;
void _DFS(int k)
{
if(k>M)
{
memcpy(path,match,sizeof(match));
ans++;
ANS=N; //记录取得答案的分隔符
return;
}
bool ft[30];
char mt[30];
memcpy(ft,f,sizeof(f));
memcpy(mt,match,sizeof(match));
for(int i=1;i<=Q;i++)
{
memcpy(f,ft,sizeof(ft));
memcpy(match,mt,sizeof(mt));
int j,p=1;
for(j=k;v[j]!=N&&j<=M;j++);
j--;
if((j+1-k)!=w[i].length())
continue;
for(int o=k;o<=j;o++)
{
int oi=o+1-k;
//剪枝
if((('a'<=match[v[o]]&&match[v[o]]<='z')||(f[w[i][oi-1]-'a']==true))&&match[v[o]]!=w[i][oi-1])
{
p=0;
break;
}
match[v[o]]=w[i][oi-1];
f[w[i][oi-1]-'a']=true;
}
if(p==0)
continue;
_DFS(j+2);
if(ans>=2)
return;
}
}
void _init()
{
ans=0;
memset(path,0,sizeof(path)); //注意初始化数组
memset(v,0,sizeof(v));
scanf("%d%d",&N,&M);
for(int i=1;i<=M;i++)
scanf("%d",&v[i]);
scanf("%d",&Q);
for(int i=1;i<=Q;i++)
cin>>w[i];
Nt=N;
}
void _solve()
{
for(N=1;N<=Nt;N++) //枚举哪个数字作为分隔符
{
for(int i=1;i<=Q;i++)
{
int j,p=1;
memset(match,0,sizeof(match));
memset(f,0,sizeof(f));
for(j=1;v[j]!=N;j++);
j--;
if(j!=w[i].length())
continue;
for(int o=1;o<=j;o++)
{
if((('a'<=match[v[o]]&&match[v[o]]<='z')||(f[w[i][o-1]-'a']==true))&&match[v[o]]!=w[i][o-1])
{
p=0;
break;
}
match[v[o]]=w[i][o-1];
f[w[i][o-1]-'a']=true;
}
if(p==0) continue;
_DFS(j+2);
if(ans>=2)
{
printf("Cannot determine\n");
return;
}
}
}
if(ans)
{
for(int i=1;i<=M;i++)
{
if(v[i]==ANS)
printf(" ");
else if('a'<=path[v[i]]&&path[v[i]]<='z')
printf("%c",path[v[i]]);
}
printf("\n");
}
else
printf("No solution\n");
}
int main()
{
scanf("%d",&T);
for(int i=1;i<=T;i++)
{
_init();
_solve();
}
return 0;
}