CF1446B 抓作弊者
1 CF1446B 抓作弊者
2 题目概要
给出两个字符串 \(𝐴\) 和 \(𝐵\) 代表两个疑似作弊学生的论文。对于任何两个字符串 \(𝐶\),\(𝐷\),我们将它们的相似性评分 \(𝑆(𝐶,𝐷)\) 定义为 \(4⋅𝐿𝐶𝑆(𝐶,𝐷)\),其中 \(𝐿𝐶𝑆(𝐶,𝐷)\) 表示字符串 \(C\) 和 \(𝐷\) 的最长公共子序列的长度。
我们相信只会复制部分文章,所以我们只对它们的子串感兴趣。
计算所有子串对的最大相似性评分。准确来说,在所有 \((𝐶,𝐷)\) 输出最大 \(𝑆(𝐶,𝐷)\),其中 \(𝐶\) 是 \(𝐴\) 的部分子串,\(𝐷\) 是 \(B\) 的部分子串。
如果 \(𝑋\) 是一个字符串,\(|𝑋|\) 表示它的长度。
如果 \(a\) 可以通过从 \(b\) 的开头或者结尾删除几个(既可以是零个也可以是全部)字符产生,那么 \(a\) 就是 \(b\) 的子串。
如果 \(a\) 可以通过从 \(b\) 中删除几个(既可以是零个也可以是全部)字符产生,那么 \(a\) 就是 \(b\) 的子序列。
注意子串和子序列是不同的。
3 解题思路
这道题与 \(LCS\) 问题十分相似。我们就可以考虑用类似的转移方程来进行 \(dp\)。我们先来看 \(LCS\) 的状态与转移方程。
\(dp[i][j]\) 表示 \(A\) 串的前 \(i\) 位与 \(B\) 串的前 \(j\) 位的最长公共子序列的长度。转移方程是:
从这里出发,我们来想一想对于这道题,我们应该怎么设计状态与转移方程。
状态似乎直接照搬就好了:\(dp[i][j]\) 表示 \(A\) 串的前 \(i\) 位与 \(B\) 串的前 \(j\) 位产生的最大价值。
按照这个思路,似乎转移方程也出来了:
目标就是 \(dp[n][m]\)(\(n\) 表示 \(A\) 串的长度,\(m\) 表示 \(B\) 串的长度)。
这里对这个方程作下解释:当 \(A\) 串第 \(i\) 位与 \(B\) 串第 \(j\) 位不同时,最大价值是 \(A\) 串前 \(i-1\) 位和 \(B\) 串前 \(j\) 位产生的最大价值与 \(A\) 串前 \(i\) 位和 \(B\) 串前 \(j-1\) 位产生的最大价值的最大值 \(-1\)。这是因为此时的最大相似度没有增加,而对答案作出贡献的 \(A\) 串或 \(B\) 串的长度增加了 \(1\),最终导致产生的价值需要 \(-1\)。而当这两位相同时,产生的最大价值就是 \(A\) 串前 \(i-1\) 位和 \(B\) 串前 \(j-1\) 位产生的最大价值 \(+2\)。这是因为相似度增加了 \(1\) 而长度增加了 \(2\)。因为相似度有一个 \(4\) 的系数,所以最终产生的价值就是 \(1*4-2 = 2\)。
但是,有一个问题。我们推几组样例就会发现,有些时候,舍弃掉之前的进度自己另起炉灶计算最大价值可能会更优。而这些情况在什么地方发生呢?就是在当当前值小于 \(0\) 的时候。这些时候,我们发现:不如舍弃掉之前的最大价值,自己从 \(0\) 开始。从 \(0\) 开始是合法的,实际意义便是选取两个空串的最大价值,所以方程应该改为:
就在我们兴高采烈地写完代码交上去后,发现\(WA\)了,什么原因呢?
测下第二个样例,发现输出是\(7\)。
这个错误惊人地与历史研究相似:当我们要寻找一个朝代的巅峰到底如何时,我们不能只看在它灭亡之前,它有着多少物资与兵力。像当年汉武帝发布了一系列有利于国家的政策,卫青、霍去病两位名将为他打跑了匈奴,创造了辉煌的大汉王朝。但是,只有汉武帝一个人没有什么用处:随着后来几个皇帝的不理朝政,西汉很快没落了,在公元 \(8\) 年,被王莽篡权。为了恢复大汉王朝,刘秀将王莽赶下了台,相当于从 \(0\) 开始重建这个帝国。此后虽然东汉也极为辉煌,但终不敌汉武帝时期的大汉王朝。那么,我们在寻找整个汉朝的巅峰时,便不能只看东汉的巅峰。而是要将每一个时期都仔细分析,最终决策出汉武帝才是汉朝的巅峰。
我们这里的错误便是如此。有可能我们在之前创造过更大的价值,但在不停的字符失配后,它不得不从 \(0\) 开始。如果此后并没有超过之前的价值,那么我们最终的答案便小于之前的那个更大的价值,导致答案偏小。对于这个问题,解决方案其实很简单:我们的目标不再是 \(dp[n][m]\) 了,而是 \(max\{dp[i][j]\}\),所有的最大价值中的最大值。
4 代码实现
代码(空格警告):
#include <iostream>
using namespace std;
const int N = 5005;
int n, m, maxn;
char A[N], B[N];
int dp[N][N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> A[i];
for (int i = 1; i <= m; i++) cin >> B[i];
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (A[i] == B[j]) dp[i][j] = dp[i-1][j-1] + 2;
else dp[i][j] = max(0, max(dp[i-1][j], dp[i][j-1])-1);
maxn = max(maxn, dp[i][j]);
}
}
cout << maxn;
return 0;
}