题解五:P1019 [NOIP 2000 提高组] 单词接龙
__yiLIUyi__ · · 题解
题目传送门:P1019 [NOIP 2000 提高组] 单词接龙。
2025.8.11 第一次修改:调整部分排版;更改、增加一些内容;修改两处笔误。
题意
给出
连接时要注意:
- 每个单词最多使用两次;
- 第一个单词要以
char 开头; - 后一个单词的开头部分应与前一个单词的结尾部分相同(也就是题目中所说的“重合部分”);
- 应保证重合部分的长度最多必须小于这两个单词的长度。或者说,两个单词不能互相包含(否则连接没有意义);
- 连接后,重合的部分会被覆盖。
思路
搜索。
我们依次枚举所有可能的排列情况,每一次都更新最大长度
首先,我们需要一个函数,得到已有的总字符串和新加入的单词的重合部分长度大小(有几位是重合的)。如果这个长度为
ll check(string s1,string s2){
ll num=min(s1.size(),s2.size());
for(ll i=1;i<num;i++){//枚举可能的长度
bool f=true;//用来判断
for(ll j=0;j<i;j++)//对比每一位
if(s1[s1.size()-i+j]!=s2[j]){
f=false;//如果不相同则判断为否
break;
}
if(f==true) return i;//如果判断为正,返回重合部分的长度
}return 0;//否则说明不能被添加
}
::::info[关于高亮行] 注意这一行的判断:
if(s1[s1.size()-i+j]!=s2[j])
这里 s1.size() 是字符串的长度,s1.size()-i 是字符串倒数第 s1.size()-i+j 位就对应单词的第
对于搜索部分,我们依次枚举单词,判断是否能在总字符串后面添加。如果不行,则直接进行下一次循环;否则添加新单词,递归,回溯,再进行下一次循环。当然,如果这个单词已经使用过两次,也直接进行下一次循环。
void dfs(string st,ll num){//num 是当前总字符串的长度
len=max(num,len);//更新最大长度
for(ll i=0;i<n;i++){//枚举单词
if(a[i]>=2) continue;//判断这个单词使用次数
ll m=check(st,str[i]);//记录重合部分长度
if(m>0){//如果重合长度大于 0,也就是说可以被添加
a[i]++;//增加单词使用次数
dfs(str[i],num+str[i].size()-m);//递归
a[i]--;//回溯
}//否则代表没有重合,不操作
}return;
}
::::info[关于
代码实现
刚才思路部分已经给出了主要代码,此处只做必要标记。
// 24ms / 1.04MB / 678B C++98 O2
// 24ms / 816.00KB / 678B C++98
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[20],len,n;//a记录每个单词使用次数
string str[20];//记录每个单词
ll check(string s1,string s2){
ll num=min(s1.size(),s2.size());
for(ll i=1;i<num;i++){
bool f=true;
for(ll j=0;j<i;j++)
if(s1[s1.size()-i+j]!=s2[j]){
f=false;
break;
}
if(f==true) return i;
}return 0;
}
void dfs(string st,ll num){
len=max(num,len);
for(ll i=0;i<n;i++){
if(a[i]>=2) continue;
ll m=check(st,str[i]);
if(m>0){
a[i]++;
dfs(str[i],num+str[i].size()-m);
a[i]--;
}
}return;
}
int main(){
cin>>n;
for(ll i=0;i<=n;i++)
cin>>str[i];//特别强调:这里把最后输入的首字母作为单词中的一个输入
dfs(' '+str[n],1);
cout<<len;
return 0;
}