P8638 [蓝桥杯 2016 省 A] 密码脱落

题目描述

X 星球的考古学家发现了一批古代留下来的密码。

这些密码是由 A、B、C、D 四种植物的种子串成的序列。

仔细分析发现,这些密码串当初应该是前后对称的(也就是我们说的回文串)。

由于年代久远,其中许多种子脱落了,因而可能会失去镜像的特征。

你的任务是:

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

输入格式

输入一行,表示现在看到的密码串。(长度不大于 )

输出格式

要求输出一个正整数,表示至少脱落了多少个种子。

输入输出样例 #1

输入 #1

ABCBA

输出 #1

0

输入输出样例 #2

输入 #2

ABDCDCBABC

输出 #2

3

说明/提示

蓝桥杯 2016 年省赛 A 组 I 题。

解析

怎样拆解题目?可以将题目转换成给定一个字符串,你最少需要往里面插入/补充多少个字符才能让它重新变成回文串

对于现在的密码串,里面没有脱落的那些字符,它们在原本的密码里,一定也构成了一个回文串。

为了让“脱落的字符”尽可能少,我们就必须让“没有脱落的字符”尽可能多!

所以,问题就转化成了:在当前这个字符串中,找出最长的一个“回文子序列”。(注意:子序列可以是不连续的,这就完美对应了中间有字符脱落的情况)。

所以最终就是转化成最少脱落的种子数 = 当前字符串的总长度 - 最长回文子序列的长度

接下来就是无脑套模板:找最长公共子序列

一个字符串 的“最长回文子序列”长度,等于 与 的反转字符串 之间的“最长公共子序列(LCS)”长度

  • 步骤 1:拿到原字符串 。
  • 步骤 2:把 倒过来,得到反转字符串 。
  • 步骤 3:求 和 的最长公共子序列长度 LCS_len。
  • 步骤 4:输出答案 S的总长度 - LCS_len。

状态转移方程: 设 dp[i][j] 表示字符串 的前 个字符和字符串 的前 个字符的最长公共子序列长度。

  • 如果 S[i-1] == R[j-1],说明这两个字符可以配对:dp[i][j] = dp[i-1][j-1] + 1
  • 如果 S[i-1] != R[j-1],说明它们不能同时出现在公共序列中,我们取之前的最大值:dp[i][j] = max(dp[i-1][j], dp[i][j-1])

代码

//
// Created by 23264 on 2026/3/10.
//
#include<bits/stdc++.h>
using namespace std;
 
string s;
vector<vector<int>> dp;
int len;
 
void solve()
{
    string r = s;
    reverse(r.begin(),r.end());
    for (int i = 1;i <= len;i++)
    {
        for (int j = 1;j <= len;j++)
        {
            if (s[i - 1] == r[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];
}
 
int main()
{
    cin >> s;
    len = static_cast<int>(s.size());
    dp.resize(len + 1,vector<int>(len + 1,0));
    solve();
}