洛谷 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就非常的特殊了感觉
但我已经掌握了好吧
这个题单词接龙 不一定非要尾巴和第二个单词的头相同才行 只要最后一个部分相同就可以了 看下面的图上单词就是一个例子
这个题要注意的关键点就是对单词重叠部分字母个数的预处理 你该以什么方式预处理
显然 双指针 一个指针放在头单词的尾部 第二个指针放在第二个单词的头部 一个部分一个部分地预处理
333.jpg
第二个点 这个题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;
}
posted @ 2024-04-16 15:57  MsEEi  阅读(67)  评论(0)    收藏  举报