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] = \max\left\{ \begin{aligned} & dp[i-1][j] \\ & dp[i][j-1] \\ & dp[i-1][j-1] + 1 \space \space \space \space A[i] = B[j] \\ \end{aligned} \right. \]

从这里出发,我们来想一想对于这道题,我们应该怎么设计状态与转移方程。

状态似乎直接照搬就好了:\(dp[i][j]\) 表示 \(A\) 串的前 \(i\) 位与 \(B\) 串的前 \(j\) 位产生的最大价值。

按照这个思路,似乎转移方程也出来了:

\[dp[i][j] = \max\left\{ \begin{aligned} & max \{dp[i-1][j], dp[i][j-1]\} - 1 \space \space \space \space A[i] \ne B[j]\\ & dp[i-1][j-1] + 2 \space \space \space \space A[i] = B[j] \\ \end{aligned} \right. \]

目标就是 \(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\) 开始是合法的,实际意义便是选取两个空串的最大价值,所以方程应该改为:

\[dp[i][j] = \max\left\{ \begin{aligned} & max \{0, max \{dp[i-1][j], dp[i][j-1]\} - 1 \}\space \space \space \space A[i] \ne B[j]\\ & dp[i-1][j-1] + 2 \space \space \space \space A[i] = B[j] \\ \end{aligned} \right. \]

就在我们兴高采烈地写完代码交上去后,发现\(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;
}

欢迎关注我的公众号:智子笔记

posted @ 2021-02-04 23:24  David24  阅读(95)  评论(0编辑  收藏  举报