洛谷 P1019. 单词接龙 --- dfs搜索 较为复杂的预处理情况 建议复习
[NOIP2000 提高组] 单词接龙
题目背景
注意:本题为上古 NOIP 原题,不保证存在靠谱的做法能通过该数据范围下的所有数据。
本题为搜索题,本题不接受 hack 数据。关于此类题目的详细内容
NOIP2000 提高组 T3
题目描述
单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如 beast
和 astonish
,如果接成一条龙则变为 beastonish
,另外相邻的两部分不能存在包含关系,例如 at
和 atide
间不能相连。
输入格式
输入的第一行为一个单独的整数 \(n\) 表示单词数,以下 \(n\) 行每行有一个单词,输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在。
输出格式
只需输出以此字母开头的最长的“龙”的长度。
样例 #1
样例输入 #1
5
at
touch
cheat
choose
tact
a
样例输出 #1
23
提示
样例解释:连成的“龙”为 atoucheatactactouchoose
。
\(n \le 20\)。
题解
这个dfs就非常的特殊了感觉
但我已经掌握了好吧
这个题单词接龙 不一定非要尾巴和第二个单词的头相同才行 只要最后一个部分相同就可以了 看下面的图上单词就是一个例子
这个题要注意的关键点就是对单词重叠部分字母个数的预处理 你该以什么方式预处理
显然 双指针 一个指针放在头单词的尾部 第二个指针放在第二个单词的头部 一个部分一个部分地预处理
第二个点 这个题dfs开始搜的条件就不是(0, 0)了 退出的条件也不是到搜到n
你需要根据题目给的单词头 去匹配单词搜索 只要匹配的 就从当前点开始搜索 所以进入dfs就有很多情况
退出的时候是你不满足进入下一层函数搜索了 证明单词你都用过了 就return到答案了
很特殊 但大框架是一样的
本题用输入的字符串编号作为与每个字符串一一对应的表标识 dfs也利用这个标识进行搜索
#include <bits/stdc++.h>
using namespace std;
const int N = 30;
int n;
char re; //存储题目要求的接龙的首个字母
string s[N];
int fl[N][N]; //存储两个字符串的最小覆盖部分字母个数
int st[N]; //判断当前字符串有没有超过使用次数
int an, ans; //an是ans在dfs里的临时变量
int mm(int x, int y) //预处理编号为x和编号为y的字符串的最小重叠部分 对输入元素的顺序有要求
{
int cnt = 0;
for (int i = s[x].size() - 1; i >= 0; i -- )
{
for (int j = i, k = 0; j < s[x].size(); j ++ ) //每次循环要对k清零 保证每次都从第二个字符串的头字母开始判断
{
if (s[x][j] != s[y][k]) break; //判断当前位是否相等 当前位不行 break掉当前循环 字符串x上的指针向前移一位看 最后s[x].size() - 1 - j位匹配不
k ++ ; //进行到这说明没有break 第二个元素的指针移到下一位继续判断
if (j == s[x].size() - 1) //如果j指针指向了一个字符串的最后一个字母 证明从j=i到j =s[x]的.size()-1的字母都是符合的 那就可以输出重叠部分个数了
{
cnt = k; //k指针走到第二个字符串的第几个字母 重叠部分就有几个字母
return k;
}
}
}
return 0; //如果循环内还没有被return掉 那这两个字符串就没有重叠的部分
}
void dfs(int u) //输入的是当前进行到编号为u的字符串
{
for (int i = 1; i <= n; i ++ )
{
if (!st[i]) continue; //如果当前搜到的字符串已经用过两次了 跳过
if (!fl[u][i]) continue; //如果以u为头单词 i为尾单词的情况最小重合部分为0 跳过
//没有以上情况 开始搜索!
st[i] -- ; //剩余使用次数--
an += s[i].size() - fl[u][i]; //两单词合并再减去重合长度
dfs(i);
st[i] ++ ; //恢复现场
an -= s[i].size() - fl[u][i]; //恢复现场
}
//当进行到这里证明循环已经结束 所有可能情况都return了 或者 进行到最后一重循环for循环里全部都continue了
ans = max(ans, an);
return;
}
void solve()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ )
{
cin >> s[i];
st[i] = 2; //每个单词两次使用限制
}
cin >> re;
//预处理最小重叠部分 i也可以接他自己
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
fl[i][j] = mm(i, j);
}
}
//cout << fl[3][4] << endl;
//找题目要求的开头字母
for (int i = 1; i <= n; i ++ )
{
if(s[i][0] == re) //找到一个就用他开始搜索
{
st[i] -- ; //剩余使用次数--
an = s[i].size(); //把当前字符串长度赋值到临时答案an里 以这个字符串开始搜索
dfs(i); //以编号为i的字符串开始搜
st[i] ++ ; //恢复现场
}
}
printf("%d\n", ans);
return;
}
int main()
{
// ios::sync_with_stdio(false); 搞不懂为啥加了这两行会错 4.16因为cin cout不能与标准输入输出流(scanf printf)混用
// cin.tie(0);
solve();
return 0;
}