洛谷 P1019. 单词接龙 --- dfs搜索 较为复杂的预处理情况 建议复习

[NOIP2000 提高组] 单词接龙

题目背景

注意:本题为上古 NOIP 原题,不保证存在靠谱的做法能通过该数据范围下的所有数据。

本题为搜索题,本题不接受 hack 数据。关于此类题目的详细内容

NOIP2000 提高组 T3

题目描述

单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如 beastastonish,如果接成一条龙则变为 beastonish,另外相邻的两部分不能存在包含关系,例如 atatide 间不能相连。

输入格式

输入的第一行为一个单独的整数 \(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  阅读(14)  评论(0)    收藏  举报