【洛谷 P8638】[蓝桥杯 2016 省 A] 密码脱落 题解(字符串+动态规划+最长公共子序列)

79 阅读3分钟

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

题目描述

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

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

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

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

你的任务是:

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

输入格式

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

输出格式

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

样例 #1

样例输入 #1

ABCBA

样例输出 #1

0

样例 #2

样例输入 #2

ABDCDCBABC

样例输出 #2

3

提示

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


思路

首先从输入中读取一行字符串,这个字符串代表现在看到的密码串。然后复制这个字符串,并将复制的字符串反转。这样做的目的是为了后面比较原字符串和反转字符串中对应位置的字符是否相等。接着,给这两个字符串前面都添加一个空格,这样方便后面的动态规划计算。

定义一个二维数组dp,dp[i][j]dp[i][j] 表示原字符串前 ii 个字符和反转字符串前 jj 个字符的最长公共子序列的长度。初始化dp数组所有元素为0。

状态转移方程如下:

  1. 如果原字符串的第 ii 个字符和反转字符串的第 jj 个字符相等:
dp[i][j]=dp[i1][j1]+1dp[i][j] = dp[i - 1][j - 1] + 1
  1. 如果原字符串的第 ii 个字符和反转字符串的第 jj 个字符不相等:
dp[i][j]=max(dp[i1][j],dp[i][j1])dp[i][j] = \max(dp[i - 1][j], dp[i][j - 1])

对于原字符串和反转字符串的每个字符,如果它们相等,那么dp[i][j]就等于dp[i-1][j-1]+1,否则dp[i][j]就等于dp[i-1][j]和dp[i][j-1]中的较大值。这是因为如果原字符串的第i个字符和反转字符串的第j个字符相等,那么它们就可以组成一个新的公共子序列,所以dp[i][j]等于dp[i-1][j-1]+1;如果它们不相等,那么dp[i][j]就应该等于dp[i-1][j]和dp[i][j-1]中的较大值,这代表了不包括当前字符的最长公共子序列的长度。

最后,输出原字符串的长度减去dp[len][len]的值,这个值就是从当初的状态,它要至少脱落多少个种子,才可能会变成现在的样子。


AC代码

#include <algorithm>
#include <cstring>
#include <iostream>
#define AUTHOR "HEX9CF"
using namespace std;
using ll = long long;

const int N = 1e3 + 7;
const int INF = 0x3f3f3f3f;
const ll MOD = 1e9 + 7;

string s1, s2;
ll dp[N][N];

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	memset(dp, 0, sizeof(dp));

	cin >> s1;
	s2 = s1;
	reverse(s2.begin(), s2.end());
	int len = s1.length();
	s1 = " " + s1;
	s2 = " " + s2;

	for (int i = 1; i <= len; i++) {
		for (int j = 1; j <= len; j++) {
			if (s1[i] == s2[j]) {
				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] << "\n";
	return 0;
}