题目描述
单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如 beast
和 astonish
,如果接成一条龙则变为 beastonish
,另外相邻的两部分不能存在包含关系,例如 at
和 atide
间不能相连。
输入格式
输入的第一行为一个单独的整数 n 表示单词数,以下 n 行每行有一个单词,输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在。
输出格式
只需输出以此字母开头的最长的“龙”的长度。
输入输出样例
输入 #1
5 at touch cheat choose tact a
输出 #1
23
说明/提示
样例解释:连成的“龙”为 atoucheatactactouchoose
。
n≤20。
解题思路
这个题其实我个人觉得和全排列有点像,无非就是把这几个单词全排列去找最长的,但是如果不做任何剪枝的话基本上就是在2的20次方量级的计算量,那是铁超时的,所以我们得想办法去做剪枝才行,第一个可以做剪枝的地方也是很容易想到的,就是如果两个单词不能接龙那么就直接continue就可以了,其实这一步就已经做了大量的剪枝,然后就是题目要求,每个单词最多可以用两次,这个我第一次做的时候忽略了导致错误,这个点需要用一个vis数组来记录每个单词的使用次数,接下来就是递归函数设计部分,由于这些单词我们都可以存到一个数组中去,所以我们dfs的参数可以直接设置成(1,当前单词所在下标;2,当前层递归时单词的长度)。
在判断两个单词是否可以相连的时候我们要理解其中的本质,能否相连其实就是判断前面一个单词的后缀和后面一个单词的前缀是否相同,而我们需要注意的是,我们题目所求的是要接龙队列长度最长,那么我们求的肯定是最短的相同部分长度,所以我们找到一个前后缀相同的点时就应该马上返回。
解题代码
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 20 + 5;
string words[MAXN], head;
int vis[MAXN], dis[MAXN][MAXN];
int n, ans = 0;
int getDis(string &s1, string &s2) {
//前面一个字符串从最后一个开始(后缀)
for (int i = s1.size() - 1; i > 0; i--) {
bool flag = true;
//后面一个字符串从前面开始
for (int j = 0; j < s1.size() - i; j++) {
//如果相等了就继续往后看
if (s1[i + j] == s2[j]) continue;
//不相等的话标记一下flag直接退出即可
flag = false;
break;
}
//都相等证明找到了前缀,s1,size() - i就是s1的后缀的长度
if (flag == true) return s1.size() - i;//相同的最短前缀长度
}
return 0;
}
void dfs(int ind, int len) {
//当前这个单词使用的次数+1
vis[ind]++;
//ans保存最大结果
ans = max(ans, len);
//遍历这些单词
for (int i = 0; i < n; i++) {
//i无法接在ind上,直接continue跳过即可
if (dis[ind][i] == 0) continue;
//用了两次了,直接跳过即可
if (vis[i] == 2) continue;
dfs(i, len + words[i].size() - dis[ind][i]);//继续递归遍历剩下的所有方案
}
//回溯的时候用过的次数减一,这样相当于从当前的接龙队列中退出
vis[ind]--;
return ;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> words[i];
cin >> head;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
dis[i][j] = getDis(words[i], words[j]);
}
}
for (int i = 0; i < n; i++) {
if (words[i][0] != head[0]) continue;
dfs(i, words[i].size());
}
cout << ans << endl;
return 0;
}