单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如 beast
和 astonish
,如果接成一条龙则变为 beastonish
,另外相邻的两部分不能存在包含关系,例如 at
和 atide
间不能相连。
输入格式
输入的第一行为一个单独的整数 n 表示单词数,以下 n 行每行有一个单词,输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在。
输出格式
只需输出以此字母开头的最长的“龙”的长度。
输入输出样例
输入
5 at touch cheat choose tact a
输出
23
说明/提示
样例解释:连成的“龙”为 atoucheatactactouchoose
。
n≤20
思路
遍历,深搜和递归。
分析题目,拆分一下要求。
最主要的是判断两个单词的最小覆盖部分,并且将覆盖后的结果继续遍历。那么很容易可以想到递归,通过递归来进行最小覆盖的连续判断,即在一个单词和另一个单词的覆盖判断成功后保留结果,并且进行深搜。
继续往下,结果所要的是返回最长的字符串,且要求同一个单词最多出现两次,那么就需要一个用来判断覆盖部分和总的字符串长度的函数。
那么步骤如下:
1.首先创建一个函数用来判断两个单词间是否存在接龙关系,并且返回接龙过程中重合的字符数,
代码如下:
int overlap_judge(int a, int b)//判断重叠部分的长度
{
int yc=0;
bool pk=true;
for (int ks = word[a].size() - 1; ks >= 0; ks--)//从前一个单词的最后开始遍历
{
for (int k = ks; k < word[a].size(); k++)
{
if (word[a][k] != word[b][yc++])//把前一个单词的结尾和后一个单词的开头一一对照,一旦有不符合的部分就结束后面的对照
{
pk = false;
break;
}
}
yc = 0;//让对照关系重新回到后一个单词的开头
if (pk == true)
return word[a].size() - ks;//一旦存在未被打断的接龙关系,就返回重合的字符数
pk = true;
}
return 0;//如果前一个单词遍历完还没有结果,则输出0
}
2.接着在判断重合字符的函数前提下,进行深搜,即判断接龙能够形成的最长字符串,
代码如下:
void scan_overlap(int p,int n)//参数为尾单词的序号和总单词个数
{
flag = false;//判断递归中止的标记
for (int j = 0; j < n; j++)
{
if (vis[j] >= 2)continue;//一个单词出现两次则跳过本次循环
if (overlap_judge(p, j) == word[p].size() || overlap_judge(p,j)==word[j].size())continue;//包含关系跳过本次循环
if (overlap_judge(p, j) == 0)continue;//不存在接龙关系
flag = true;//确认此时的两个单词可以接龙
an += word[j].size() - overlap_judge(p, j);//单词长度计算
vis[j]++;//单词的使用次数加一
scan_overlap(j, n);//继续接龙
an -= word[j].size() - overlap_judge(p, j);//深搜的节点回归
vis[j]--;
}
if (flag == false)
ans = max(an, ans);//更新目前最长单词长度
return;
}
3.主函数,输入和输出,还有根据输入的字母寻找单词为接龙的“龙头”
代码如下:
int main()
{
int n;
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> word[i];
}
char begin_word;
cin >> begin_word;
for (int i = 0; i < n; i++)
for (int k = 0; k < n; k++)
mc[i][k] = overlap_judge(i, k);//预处理,可以省略
for (int i = 0; i < n; i++)
{
if (word[i][0] == begin_word)//根据给出的首字母找单词
{
an = word[i].size();//更新字符长度
vis[i]++;//单词使用次数加一
scan_overlap(i, n);//深搜
vis[i] = 0;//清空使用次数,为了对首字母相同的不同单词为“龙头”的情况进行判断
}
}
cout << ans;
return 0;
}
总代码:
#include<iostream>
#include<string>
#include<cmath>
using namespace std;
string word[20];
bool flag;
int mc[20][20];
int vis[20];
int ans =-1;
int an = 0;
int overlap_judge(int a, int b)//判断重叠部分的长度
{
int yc=0;
bool pk=true;
for (int ks = word[a].size() - 1; ks >= 0; ks--)
{
for (int k = ks; k < word[a].size(); k++)
{
if (word[a][k] != word[b][yc++])
{
pk = false;
break;
}
}
yc = 0;
if (pk == true)
return word[a].size() - ks;
pk = true;
}
return 0;
}
void scan_overlap(int p,int n)//参数为尾单词的序号和总单词个数
{
flag = false;//判断递归中止的标记
for (int j = 0; j < n; j++)
{
if (vis[j] >= 2)continue;
if (overlap_judge(p, j) == word[p].size() || overlap_judge(p,j)==word[j].size())continue;
if (overlap_judge(p, j) == 0)continue;
flag = true;
an += word[j].size() - overlap_judge(p, j);
vis[j]++;
scan_overlap(j, n);
an -= word[j].size() - overlap_judge(p, j);
vis[j]--;
}
if (flag == false)
ans = max(an, ans);
return;
}
int main()
{
int n;
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> word[i];
}
char begin_word;
cin >> begin_word;
for (int i = 0; i < n; i++)
for (int k = 0; k < n; k++)
mc[i][k] = overlap_judge(i, k);
for (int i = 0; i < n; i++)
{
if (word[i][0] == begin_word)
{
an = word[i].size();
vis[i]++;
scan_overlap(i, n);
vis[i] = 0;
}
}
cout << ans;
return 0;
}
个人的注意点:
在判断条件中,“||”的优先级是高于“==”的即
if(overlap_judge(p,j)==word[p].size() || word[j].size())
if(overlap_judge(p,j)==word[p].size() || overlap_judge(p,j)==word[j].size())
的结果是不一样的。