P2432 zxbsmk爱查错,字符串线性dp
P2432 zxbsmk爱查错 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目背景
zxbsmk在暑假回了一趟老家,见到了自己的外婆。然而他遇到了一个问题。
题目描述
外婆很喜欢给别人写信,然而因为年纪大了,信里面充斥着各种错误。所以zxbsmk打算帮外婆修正每个错误的句子。
因为外婆的文化水平很高,所以她写的句子都是英文句子,并且句子内的字母都是小写字母。然而zxbsmk的英语水平非常低,所以他买来了一本英语词典,词典里一共有W (1<=W<=600) 个单词,每个单词的长度都不超过25个字母,而且都是由小写字母组成的。
那么再来看一下外婆的句子都有什么错误。例如,外婆写的一个句子是"catotail",这并没有什么意义,因为一个多余的字母"o"出现了,所以正确的句子应该是"cattail"。
已知外婆的句子是由L (2<=L<=300) 个字母组成的。其中有一些字母是多余的。现在,请你借助zxbsmk的词典,帮助他修正外婆的句子。而且你必须尽量少地去除多余的字母,使剩余的字母是一个合法的句子。
输入格式
第一行:输入两个整数W和L。
第二行:输入L个字符,表示需要修正的句子。
之后的W行,每行输入一个合法的单词。
输出格式
一个整数,表示最少需要剔除的字母数。
输入输出样例
输入 #1复制
2 8 catotail cat tail
输出 #1复制
1
说明/提示
catotail —> cattail
解析:
1. 思考解法
- 文本串后面的内容不会影响文本串前半部分的最优解,符合无后效性。(实际上有后效性也可以只不过很复杂)
- 若把文本串右端位置作为状态,文本串右端位置较靠右的状态需要通过文本串右端位置较靠左的状态得到(如 di 需要通过 d0…di−1 的其中之一得到),符合子问题重叠性。
最简单的子集划分方式:f[i]:表示以 i 结尾的子串最少需要删除多少个字符才能形成一段话。
很明显,这是一个不重不漏的划分方式,现在我们来看看这种划分方式是否能写出状态转移方程:
f[i]可能等于f[i-1]+1;
也可能等于:f[len]+i-len-strlen(w[j]):表示 f[i] 由以 f[len] 结尾的子串转移而来,而在 L[len] 到 L[i] 之间,可以处理出单词 w[j],
这其中需要用到字符串匹配,我们可以使用暴力匹配的方式,因为数据量较小,时间复杂度是 O(25*L*W)<5400000
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<math.h>
#include<map>
using namespace std;
typedef long long LL;
const int N = 605;
int w, L;
char s[N];
char c[N][30];
int f[N];
int main() {
scanf("%d%d", &w, &L);
scanf("%s", s + 1);
for (int i = 1; i <= w; i++) {
scanf("%s", c[i] + 1);
}
memset(f, 0x3f3f3f3f, sizeof f);
f[0] = 0;
for (int i = 1; i <= L; i++) {
f[i] = f[i - 1] + 1;
for (int j = 1; j <= w; j++) {
int len = i;
int k = strlen(c[j] + 1);
while (k > 0 && len > 0) {
if (c[j][k] == s[len])len--, k--;
else len--;
}
if (!k) f[i] = min(f[i], f[len] + i - len - (int)strlen(c[j]+1));
}
}
cout << f[L] << endl;
return 0;
}