题目
单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如 beastbeast和astonishastonish,如果接成一条龙则变为beastonishbeastonish,另外相邻的两部分不能存在包含关系,例如atat 和 atideatide 间不能相连。
输入格式:
输入的第一行为一个单独的整数nn (n \le 20n≤20)表示单词数,以下nn 行每行有一个单词,输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在.
输出格式:
只需输出以此字母开头的最长的“龙”的长度
输入输出样例
输入样例#1:
5
at
touch
cheat
choose
tact
a
输出样例#1:
23
分析
题目大意
题目很好理解,单词接龙,需要注意的是题目给出了“龙头”,并且这个龙头是真的,也就是说需要按照这个龙头去找第一个单词,不然可能通过不了。
解题思路
每个单词可以使用两次,所以需要一个数组去存储每个单词被访问的次数,作为递归边界的判定依据。根据题意,我们还需要知道每个单词后缀与每个单词前缀重叠的最小长度,这里是单独写了一个函数,在每次遍历的时候进行调用。其余就是dfs的事了,具体细节可以见我的代码注释。
代码
//P1019
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
const int M = 20+5;
int n;
string words[M];
int vis[M]; //每个单词的访问次数
int ans = 0;
int cmp(string a,string b){
int l = (int)min(a.length(), b.length());
for(int i = 1; i < l ; i++) { //重叠的个数,一定是比较短的单词长度小1,避免其被包含
int j;
int flag = 1;
for(int j = 0; j < i; j++)
if(a[a.length() - i + j] != b[j]) //a的尾端与b首段相比较
flag = 0;
if(flag)
return i;
}
return 0;
}
void dfs(string s,int l){
ans = max(ans,l); //更新最大值
for(int j = 0;j < n;j ++){
if(vis[j] >= 2){ //每个单词的使用次数不能超过2次
continue;
}
int c = cmp(s,words[j]); //取得最小重叠次数
if(c > 0){
vis[j]++;
dfs(words[j],l+words[j].length()-c); //继续搜索下一个单词,长度需要减去重叠的个数
vis[j]--;
}
}
}
int main(){
cin>>n;
for(int i = 0;i < n+1;i++){ //最后一位是龙头
cin>>words[i];
}
dfs(" "+words[n],1); //利用龙头去找第一个,左边的空格只要不是英文字母就可以,目的是便于寻找龙头后面的一个单词,这是因为其余单词都不能互相包含
cout<<ans<<endl;
return 0;
}