题目描述
单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如 "beast" 和 "astonish",如果接成一条龙则变为 "beastonish",另外相邻的两部分不能存在包含关系,例如 "at"和 "atide"间不能相连。
输入格式 输入的第一行为一个单独的整数n表示单词数,以下n行每行有一个单词,输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在。
输出格式 只需输出以此字母开头的最长的“龙”的长度。
样例输入
5
at
touch
cheat
choose
tact
a
样例输出
23
提示
样例解释:连成的“龙”为 "atoucheatactactouchoose",n≤20
拿到这道题,我们首先分析题目,有三个关键点:
1最后会输入一个给定字母作为”龙“的头 2若两个单词之间是包含关系不能相连,每个单词可以用两次 3像"touch"和"choose"这样公共部分有多个字母的也可以相连,一定要让公共部分尽可能少,这样 才能使最后龙的长度最长
看到数据范围这么小(n≤20),我们就可以考虑用搜索的方法的来解决这道题:
只要是两个单词之间有公共部分却又不存在包含关系的就可以进行搜索,如果发现不存在任何一个单词可以进行接龙就停止搜索,记录现在单词长度与之前最大长度的最大值,即max(ans,len)。以上就是本题的核心思路。
相信搜索对于各位聪明的小伙伴来说一点也不难,但问题是如何找到两个单词的公共部分呢?我们通过一个字符串的函数,可以轻而易举地实现这一操作。
我们假定有两个字符串a,b(string类型),我们现在要找它们的公共部分: substr(n,m)表示从该字符串的第n个位置(0为第一个位置)以后(包括n在内)m个字符。那么用如下代码就可以实现寻找两个单词的最短公共部分:
string a,b;
for(k=1;k<min(a.size(),b.size());k++){
if(a.substr(a.size()-k,k)==b.substr(0,k)){
break;
}
}
因为数据范围较小,所以可以采用枚举的方法,k就是我们最后要找的最短公共部分。
完整代码如下:
#include<bits/stdc++.h>
using namespace std;
int f[25];//标记单词用了几次
int g[25][25];//两个单词的最短公共部分
int n,ans;//n个单词和最终答案(最大值)
string a[25];//存储20个单词
void work(int t,int len){//分别表示上一个单词在string数组里存储的位置和现在"龙"的长度
int i,j,k;
int flag=1;//标记
for(i=1;i<=n;i++){
if(g[t][i]!=0&&f[i]<2){
f[i]++;//标记使用次数
flag=0;//表示存在可以接龙的单词
work(i,len+a[i].size()-g[t][i]);
//将现在单词的序号作为下一次搜索的上一个单词序号,增加接龙的长度
f[i]--;//回溯
}
}
if(flag==1){//如果没有能够接龙的单词了
ans=max(ans,len);//记录"龙"的最大长度
}
}
int main(){
int i,j,k;
cin>>n;
for(i=1;i<=n;i++){//输入
cin>>a[i];
}
char c;//输入接龙开头的字母
cin>>c;
for(i=1;i<=n;i++){
for(j=1;j<=n;j++){
for(k=1;k<min(a[i].size(),a[j].size());k++){
if(a[i].substr(a[i].size()-k,k)==a[j].substr(0,k)){
g[i][j]=k;//记录最短公共长度
break;
}
}
}
}
for(i=1;i<=n;i++){
if(c==a[i][0]){//从开头字母开始接龙第一个单词
f[i]=1;//千万别忘记这里标记使用次数
work(i,a[i].size());//搜索
f[i]=0;//还要清零
}
}
cout<<ans;
return 0;
}
看懂了的小伙伴快去试试吧:
[NOIP2000 提高组] 单词接龙 - 洛谷https://www.luogu.com.cn/problem/P1019最后希望大家能够喜欢我的文章,多多支持我,有问题的小伙伴可以私信交流,大家共同进步!