题解:P1019 [NOIP2000 提高组] 单词接龙

目录

题目

题目描述

输入格式

输出格式

输入输出样例

输入样例#1

输出样例#1

说明/提示

思路

一. 审题

二. 构成

1.主函数

2.dra函数

3.DFS

 代码

一. 部分代码实现

1.主函数及全局变量

2.dra函数

 3.DFS

二. 最终代码


题目

题目描述

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

输入格式

输入的第一行为一个单独的整数 n 表示单词数,以下 n 行每行有一个单词,输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在。

输出格式

只需输出以此字母开头的最长的“龙”的长度。

输入输出样例

输入样例#1

5
at
touch
cheat
choose
tact
a

输出样例#1

23

说明/提示

样例解释:连成的“龙”为 atoucheatactactouchoose

n\leqslant 20

思路

一. 审题

从题目条件当中,我们可以得出以下条件:

1.每个单词最多用2次。

2.有的单词自己可以连接自己(比如说"tact",它可以以"t"为重合部分进行连接)。

3.重合部分越小越好。

4.两个单词之间不能存在包含关系(也就是说最小重合部分不能为这两个单词中较短单词的长度)。

二. 构成

本题的代码可以分成以下部分

①输入n个单词

②执行查找每两个单词之间的重合部分的函数dra

③运用DFS搜索来“接龙”,找出“龙”的最大长度

1.主函数

我们使用string s[n]来存储这n个单词,之后输入n,再分别输入这n个单词,之后,我们用执行dra函数来查找两个单词(可以为同一个单词)之间的重合部分,并用g[i][j]来表示第i个和第j个单词之间的重合部分,再在这n个单词中寻找开头为“龙头”的单词,如果找到了,就开始DFS来“接龙”寻求最大长度。

2.dra函数

这里以字符串a与字符串b为例。

我们从1开始,不断加大假设的重合部分的长度i,将字符串a中以倒数第i个字母为开头的长度为i的字串字符串b中以第0个字母(毕竟输入字符串时第一个字符的位置一般为0)为开头的长度为i的字串进行比较,如果相符,就直接判定这个字串是字符串a与字符串b的重合部分(因为字串长度要越小越好,所以找到重合部分就立即停止),但是要先检查字串的长度是否等于两个字符串之间较短的字符串的长度,如果等于,那么就说明这两个字符串存在包含关系,并将其视作为没有重合部分,否则就这个字串的长度作为返回值赋值于g[i][j]

关于字串,这里要简单介绍一个字符串函数substr()

substr()就是用来表示从某个字符串的某个位置为开始,长度为某个数值的字串,格式为:

字符串.substr(字串第一个字符在该字符串的位置,字串的长度);

3.DFS

在开始DFS之前,要用数组f[i]来表示这个第i个单词被使用的次数,用整形变量maxl表示“龙”的最大长度(注意:刚才提到的要建立的数组和变量是全局变量),除了在主函数部分提到的寻找“龙头”,还需要将这个单词的使用次数增加到1,避免出现该单词的第三次使用,在DFS执行过后,还要将之前增加的1次使用次数减去,以免后患。

接下来,才是正式的DFS函数部分:

由于我们最终改变的数值是全局变量maxl,所以我们将返回值类型定义为void,参数设定为选中的单词的序数t,以及“龙”的长度l

首先,选中“龙头”的单词,传递参数为“龙头”单词的序数i(作为最开始的选中的单词)和该单词的长度(作为最开始的“龙”的长度),之后,递归执行以下内容:

首先比较“龙”的长度l与最大长度maxl,如果l> maxl,那么maxl=l,之后我们查找n个单词中(包含选中的单词)使用次数不为2且与选中单词的重合部分长度不为0的单词,将其选中“接龙”,把目前“龙”的长度加上刚刚选中的单词的长度并减去重合部分的长度,就是现在“龙”的长度,之后调用DFS函数,传递参数为刚才选中的单词的序数i以及目前“龙”的长度l,重复上述操作直到没有可选的单词(循环结束),再进行return; 回到上一层递归。

注:你现在可以选择根据上面的条件写出代码。

 代码

一. 部分代码实现

1.主函数及全局变量

string s[21];//表示n个单词
char c;//表示“龙头”的字符
int g[21][21],n,f[21],maxl;//g[i][j]表示第i个单词与第j个单词的重合部分,f[i]表示第i个单词的使用次数,maxl表示“龙”的最大长度
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>s[i];
    }//输入n个单词
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            g[i][j]=dra(s[i],s[j]);
        }
    }//利用dra函数查找第i个单词与第j个单词之间重合部分的长度(这里i可以等于j,因为可以“自己连自己”)
    cin>>c;//输入“龙头”
    for(int i=1;i<=n;i++)
    {
        if(s[i][0]==c)//查找n个单词中有没有可以作为“龙头”的单词)
        {
            f[i]=1;//以免出现第三次使用这个单词的现象
            dfs(i,s[i].size());//dfs(选中的单词的序数,“龙”的长度(此处为初始长度))
            f[i]=0;//以免后患
        }
    }
    cout<<maxl;//输出最大长度
    return 0;
}

2.dra函数

int dra(string a,string b)
{
    int result=0;//重合长度
    for(int i=1;i<=min(a.size(),b.size());i++)//i表示假设重合部分的长度
    {
        if(a.substr(a.size()-i,i)==b.substr(0,i))//两个字串(见上文内容,这里不做赘述)
        {
            result=i;
            break;//找到重合部分立即退出(为了得到最短的重合部分长度)
        }
    }
    if(result==min(a.size(),b.size()))return 0;//如果重合部分长度为两字符串较短的字符串的长度(即存在包含关系),就视作没有重合部分。
    else return result;//否则照常返回
}

 3.DFS

void dfs(int t,int l)//t:选定的单词的序数,l:“龙”的长度
{
    if(l>maxl)maxl=l;//如果这里没有判断,即使是只有有最长的“龙头”能作为最大长度(无单词可接),也会输出0,不符合题意。
    for(int i=1;i<=n;i++)
    {
        if(f[i]!=2&&g[t][i]!=0)//寻找能够选定的单词(使用次数不为2,且有重合部分)
        {
            if((l+s[i].size()-g[t][i])>maxl)maxl=l+s[i].size()-g[t][i];//“接龙”后“龙”的长度,每到“龙”的长度发生改变就要判断是否为最大长度
            f[i]++;//增加使用次数,以防超过2次使用
            dfs(i,(l+s[i].size()-g[t][i]));//递归
            f[i]--;//消减使用次数,以防出现实际低于2次使用
        }
    }
    return;//没有单词可以使用时,返回到上一层递归
}

二. 最终代码

#include<bits/stdc++.h>
using namespace std;
string s[21];
char c;
int g[21][21],n,f[21],maxl;
void dfs(int t,int l)
{
    if(l>maxl)maxl=l;
    for(int i=1;i<=n;i++)
    {
        if(f[i]!=2&&g[t][i]!=0)
        {
            if((l+s[i].size()-g[t][i])>maxl)maxl=l+s[i].size()-g[t][i];
			f[i]++;
			dfs(i,(l+s[i].size()-g[t][i]));
			f[i]--;
        }
    }
    return;
}
int dra(string a,string b)
{
    int result=0;
    for(int i=1;i<=min(a.size(),b.size());i++)
    {
        if(a.substr(a.size()-i,i)==b.substr(0,i))
        {
            result=i;
            break;
        }
    }
    if(result==min(a.size(),b.size()))return 0;
    else return result;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>s[i];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            g[i][j]=dra(s[i],s[j]);
        }
    }
    cin>>c;
    for(int i=1;i<=n;i++)
    {
        if(s[i][0]==c)
        {
            f[i]=1;
            dfs(i,s[i].size());
            f[i]=0;
        }
    }
    cout<<maxl;
    return 0;
}

AC! 

  • 43
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值