撰写博客
题目链接:ybtoj高效进阶 21251
题目大意
给你一个大字符串和一些小字符串,然后大字符串的每个字符有修改的费用,修改可以把它修改成空格。
空格原本不出现在字符串中,然后问你要让所有的小字符串都不是大字符串的子串的最小修改费用。
思路
首先不难想出要找到小字符串在大字符串的出现,可以用 KMP 解决。
然后你考虑 DP,设
f
i
f_i
fi 为搞定前
i
i
i 个字符(最后一个一定要修改)的最小费用,那你考虑求出一个东西
r
i
r_i
ri 为如果要选字符
i
i
i,然后前面的字符可以选到哪个位置(我这里是
r
i
r_i
ri 这个位置是不能选的)
然后这个你 KMP 找出来的时候记录一下,然后记得还要前缀最大值一下。
那都前缀最大值了,那它就是递增的。
你再看
f
i
f_i
fi 怎么转移:
f
i
=
a
i
+
min
r
i
−
1
⩽
j
<
i
{
f
j
}
f_{i}=a_i+\min\limits_{r_{i-1}\leqslant j<i}\{f_j\}
fi=ai+ri−1⩽j<imin{fj}
(注意范围是
r
i
−
1
r_{i-1}
ri−1 不是
r
i
r_i
ri)
然后由于 r i − 1 r_{i-1} ri−1 递增可以用单调队列优化 DP。
然后就可以了。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n, m, a[200005], tn, r[200005];
char s[200005], t[200005];
int f[200005], sta[200005], l;
int fail[200005];
int read() {
int re = 0;
char c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
re = (re << 3) + (re << 1) + c - '0';
c = getchar();
}
return re;
}
int main() {
// freopen("wzadx.in", "r", stdin);
// freopen("wzadx.out", "w", stdout);
n = read(); m = read();
// scanf("%d %d", &n, &m);
scanf("%s", s + 1);
for (int i = 1; i <= n; i++) a[i] = read();
// for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= m; i++) {
scanf("%s", t + 1);
tn = strlen(t + 1);
int j = 0;
for (int k = 2; k <= tn; k++) {//KMP 匹配找到每个子串的出现
while (j && t[j + 1] != t[k]) j = fail[j];
if (t[j + 1] == t[k]) j++;
fail[k] = j;
}
j = 0;
for (int k = 1; k <= n; k++) {
while (j && t[j + 1] != s[k]) j = fail[j];
if (t[j + 1] == s[k]) j++;
if (j == tn) r[k] = max(r[k], k - j + 1);//找到从 k 开始最多往前保留多少个字符
}
}
for (int i = 1; i <= n + 1; i++)//前缀一下
r[i] = max(r[i], r[i - 1]);
sta[++sta[0]] = 0; l = 1;
for (int i = 1; i <= n + 1; i++) {
while (l <= sta[0] && sta[l] < r[i - 1]) l++;//单调队列优化
f[i] = a[i] + f[sta[l]];
while (l <= sta[0] && f[sta[sta[0]]] >= f[i]) sta[0]--;
sta[++sta[0]] = i;
}
printf("%d", f[sta[l]]);
return 0;
}