蓝桥杯-密码脱落(最长公共子序列)

【问题描述】

X星球的考古学家发现了一批古代留下来的密码。
这些密码是由A、B、C、D 四种植物的种子串成的序列。
仔细分析发现,这些密码串当初应该是前后对称的(也就是我们说的镜像串)。
由于年代久远,其中许多种子脱落了,因而可能会失去镜像的特征。

你的任务是:
给定一个现在看到的密码串,计算一下从当初的状态,它要至少脱落多少个种子,才可能会变成现在的样子。

输入一行,表示现在看到的密码串(长度不大于1000)
要求输出一个正整数,表示至少脱落了多少个种子。

例如,输入:

ABCBA

则程序应该输出:

0

再例如,输入:

ABDCDCBABC

则程序应该输出:

3

资源约定:
峰值内存消耗 < 256M
CPU消耗  < 3000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。

注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
注意: 所有依赖的函数必须明确地在源文件中 #include <xxx>, 不能通过工程设置而省略常用头文件。

提交时,注意选择所期望的编译器类型。

方法一:

首先输入字符串s,令i = 字符串开端,j = 字符串末尾,

如果  s[\ i\ ] == s[\ j\ ], 那就i++ , j--.

如果  s[\ i\ ]\ != s[\ j\ ] ,两种办法: i 左边补上s[j], j--;  j 右边补上s[i] ,i++;

其实没必要真的去补,比如,i 左边补s[j]时,我们可以假设补上了,只需要让j--,i 不变。

 

这种思路可以用递归实现。

但是这种方法的的时间复杂度是很高的,比如字符串为ABCDEF,那要补成ABCDEFEDCBA

由此可见,最坏的情况下,最深要搜索到第s.size()层,时间复杂度为2 ^\begin{matrix} s.size() \end{matrix}

所以当字符串长度为30的时候,时间复杂度2^\begin{matrix} 30 \end{matrix}  ≈  10 * 10^8,就要差不多10秒才能跑出来。

但是此题字符串长度最长为1000,这种方法就不可取了,但是分而治之的想法是很好的。

经测试,这种方法能得43分。

#include <iostream>
using namespace std;
string s;
int dfs(int left, int right)
{
    while (s[left] == s[right])
    {
        left++;
        right--;
    }
    if (left >= right)
        return 0;
    return min(dfs(left + 1, right) + 1, dfs(left, right - 1) + 1);//返回两条支路中,短的那条
}
int main()
{
    cin >> s;
    cout << dfs(0, s.size() - 1) << endl;
    //system("pause");
    return 0;
}

方法二:

① 把原字符串倒转过来

② 从s1中的A开始, 在s2中 找对应的 A

③ 这时候,如果让这两个A对称,可以把 CB 补到原字符串的前面

④ 然后到s1中的B,在s2中找对应的B

直接就找到了,说明两个B的位置是对称的,继续

⑤接下来是D,

可以发现,在s2中,隔着一个C,才能要找到D。

说明要想两个D对称,要在把C补到 D前面。

这时候s1已经是回文串了,一共补了三个字母。

 

这是用上找下,我们也可以用下找上,

先找,

 

 

然后补, ,因为s2是s1的翻转,为了方便观察,也可以补在s2的前面,是一样的

再找,

再补,,s2已经是回文串了,补了5个字母;

 

当然,也不用一味地用上找下,或者用下找上,也可以交叉着来

比如:

第一步用下找上

下一步可以这样用用上找下:

 s2变成了回文串,用了8个字母。

那怎么样找,才是最优解呢,

不断尝试后,我们发现了规律:

找到s1和s2的最长公共子序列,

配不上的CB和C,我们把它挪上去。

这个就是正确答案了。

s1 和 s2 的 最长公共子序列的长度 lcs = 7 , 然后 字符串的长度 len = 10, len - lcs = 3 就是正确答案。

 

问题就转换成了求最长公共子序列:

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int dp[1005][1005];
int main()
{
    string s1, s2;
    cin >> s1;
    s2 = s1;
    reverse(s2.begin(), s2.end());

    int len = s1.size();
   
    for (int i = 1; i <= len; i++)
    {
        for (int j = 1; j <= len; j++)
        {
            if (s1[i - 1] == s2[j - 1])
                dp[i][j] = dp[i - 1][j - 1] + 1;
            else
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
        }
    }
    cout << len - dp[len][len] << endl;
    //system("pause");
    return 0;
}

 

方法一虽然不能AC,但是在考场上应该是较为容易想到的,

方法二虽然能AC,但是要想到这是一个最长公共子序列的题目,对于一般人而言,,应该是很难想到的吧。。。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值