7届蓝桥杯省赛-密码脱落

目录

前言:

正文:

一、题干

二、思路:

后记:


前言:

        在这一篇文章中,我们主要介绍“密码脱落”这道题的解法

正文:

一、题干

        时间限制:1.0s   内存限制:256.0MB

        X星球的考古学家发现了一批古代留下来的密码。
        这些密码是由A、B、C、D 四种植物的种子串成的序列。
        仔细分析发现,这些密码串当初应该是前后对称的(也就是我们说的镜像串)。
        由于年代久远,其中许多种子脱落了,因而可能会失去镜像的特征。
        你的任务是:
        给定一个现在看到的密码串,计算一下从当初的状态,它要至少脱落多少个种子,才可能会变成现在的样子。
        输入一行,表示现在看到的密码串(长度不大于1000)
        要求输出一个正整数,表示至少脱落了多少个种子。
        例如,输入:
        ABCBA
        则程序应该输出:
        0
        再例如,输入:
        ABECDCBABC
        则程序应该输出:
        3
        资源约定:
        峰值内存消耗 < 256M
        CPU消耗 < 1000ms

二、思路:

        什么是子序列以及回文序列:

        ABCBAE为一个序列

        而ABE则为一个子序列(保持原有元素顺序)

        BCB则是一个回文子序列

        ABCBA则是最长回文子序列

        题目简单概括来说就是要算出现在的串添加多少个字符就可以成为原串。

        要求最少添加几个字符,我们可以先从原串中找到一个最长回文子序列,然后对于原串中不属于这个回文子序列的字符,在它关于回文子序列中心的对称位置添加一个相同字符即可。(见下样例)

        那么需要添加的字符数量即为len-最长回文子序列长度。

        (len为输入的字符串长度)

        举个例子:

        输入

        ABECDCBABC

        可以找到除黑色字体以外的为最长回文子序列。

        ABECDCBABC

        那么补齐之后的字符串则为

        CBABECDCEBABC

        而补上的CBE就是题目中所说的脱落的密码了,我们求出脱落的密码数量即可。

        那么问题就很简单了,求出最长回文子序列就可以了

        方法:动态规划

        分为两种情况考虑:

        第一个元素和最后一个元素相同,则有:(n为字符串长度)

        lps(0,n-1)=lps(1,n-2)+2

        (lps为Longest Palindromic Subsequence,最长回文子序列)

        不相同时,则有:

        lps(0,n-1) = max(lps(1,n-1), lps(0,n-2))

        也就是说可以把问题拆分开成小问题,最后使用递归就能得出最长回文子序列的长度。

        不过这样做的话会超时(指用lps函数递归),只能得到33分。

        既然用递归会超时,就得用其他方法了,但是核心思想还是没变,代码如下

#include<iostream>
#include<string>
#include<cstring>
using namespace std;

int main(int argc, char const *argv[])
{
    char s[1020];
    cin>>s;
    int len = strlen(s);
    int dp[len][len];
    memset(dp,0,sizeof(dp));
    for (int i = 0; i < len; i++)
    {
        dp[i][i] = 1;
    }
    
    for (int i = len-1; i >= 0; i--)
    {
        for (int j = i+1; j < len; j++)
        {
            if (s[i] == s[j])
            {
                dp[i][j] = dp[i+1][j-1] + 2;
            }
            else
            {
                dp[i][j] = max(dp[i+1][j],dp[i][j-1]);
            }
        }
        
    }
    
    cout<<len-dp[0][len-1];

    return 0;
}

        代码解读:

        dp[i][j]可以理解为s[i]到s[j]这段范围内的最长回文子序列的长度,其中↓这一部分就是核心代码,用于求dp[i][j]的值。

    for (int i = len-1; i >= 0; i--)
    {
        for (int j = i+1; j < len; j++)
        {
            if (s[i] == s[j])
            {
                dp[i][j] = dp[i+1][j-1] + 2;
            }
            else
            {
                dp[i][j] = max(dp[i+1][j],dp[i][j-1]);
            }
        }
        
    }

        中间的if-else结构很易懂,就是前面讲的两种情况,举例子来看更容易理解。

        假设输入ABECDCBABC,进入循环时i=9, j=10, j>len跳出,i--,i=8时正式进入循环

0

1

2

3

4

5

6

7

8

9

A

B

E

C

D

C

B

A

B

C

i

j

        开始时的dp数组:

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

        结束时的dp数组:(标红的为行列)

1

1

1

1

1

3

5

7

7

7

0

0

1

1

1

1

3

5

5

5

5

1

0

0

1

1

1

3

3

3

3

5

2

0

0

0

1

1

3

3

3

3

5

3

0

0

0

0

1

1

1

1

3

5

4

0

0

0

0

0

1

1

1

3

5

5

0

0

0

0

0

0

1

1

3

3

6

0

0

0

0

0

0

0

1

1

1

7

0

0

0

0

0

0

0

0

1

1

8

0

0

0

0

0

0

0

0

0

1

9

0

1

2

3

4

5

6

7

8

9

        简单来说就是从dp[8][9]一路算dp[7][8],dp[7][9],dp[6][7],dp[6][8],dp[6][9]。。算到dp[0][7],dp[0][8],dp[0][9]结束,dp[0][9]就是答案。

后记:

        感谢各位读到这,越做感觉自己越菜了~

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值