题解:P2432 zxbsmk爱查错

· · 题解

还算简单的dp题。

题意

给定一个长度为 \texttt{L} 的字符串和有 \texttt{M} 个单词的词典,要求从字符串中删除尽可能少的字符,使得删完后字符串是由词典里的单词连续拼接成的。

思路

考虑只有 1 维的 \texttt{dp}

状态:
###### 转移: 从 $i$ 这个位置出发,主动去更新。 $f_x \leftarrow f_i + (x - i - len_j)$。 在这条转移中,我们选择了下一个单词是词典中的第 $j$ 个单词,$x$ 是 $i$ 之后第一个出现的第 $j$ 个单词的结尾位置。$(i,x]$ 这段区间长度为 $x-i$,需要保留 $len_j$ 个字符,即需要删除 $x - i - len_j$ 个字符。 那么问题来了,怎样寻找 $x$ 呢? 可以记 $nxt_{i,j}$ 表示 $i$ 这个位置后面(不包括 $i$),第一个 $j$ 出现的位置。这个可以 $O(L)$ 预处理。具体实现:从 $L$ 到 $1$ 遍历,期间维护 $lst$ 数组来记到当前每个字母最后一个出现的位置。那么 $nxt_i$ 就是 $lst$,但要注意不要把 $i$ 也算进 $nxt_i$。 这样,我们就能找到 $x$。令 $j$ 一开始为 $i$,假设现在匹配到了第 $k$ 个单词的第 $l$ 个字符,那么 $j \leftarrow nxt_{i,t_{k,l}}$。注意特判找不到的情况。 说起来挺复杂,但其实代码没这么难。 ## 时空复杂度 ###### 时间复杂度: 输入、预处理是 $O(L\times v+len)$。$len$ 是字典中单词长度的和,$v$ 是字母的个数,也就是 $26$。 $\texttt{dp}$ 的时间复杂度是 $O(L\times len)$ 的。$len$ 的含义同上。 所以,总的时间复杂度是 $O(L \times (v+len))$。 ###### 空间复杂度: 这个就很简单了,$O(L \times v+len)$。 ## 代码实现 [AC link](https://www.luogu.com.cn/record/253261636) ``` #include <bits/stdc++.h> using namespace std; #define ll long long const int N = 700, M = 310, V = 30; int test, n, m, len[N], nxt[M][V], lst[V], f[M]; //f[i] 表示只考虑前i个字母,且i是最后一个被保留的字母,这样的情况下最少删除几个字母 char s[N], t[N][V]; ll ans = 0ll; inline void solve () { ans = 0ll; scanf("%d%d%s", &n, &m, s + 1); for (int i = 1; i <= n; i++) { scanf("%s", t[i] + 1); len[i] = strlen(t[i] + 1); } for (int i = 1; i <= 26; i++) { lst[i] = m + 1; } for (int i = m; i >= 0; i--) { memcpy(nxt[i], lst, sizeof(nxt[i])); lst[s[i] - 'a' + 1] = i; //注意先memcpy再更新lst!! } for (int i = 1; i <= m; i++) { f[i] = m; //初始全为无穷大 } f[0] = 0; for (int i = 0; i < m; i++) { for (int j = 1; j <= n; j++) { int x = i; for (int k = 1; k <= len[j]; k++) { x = nxt[x][t[j][k] - 'a' + 1]; //寻找x if (x == m + 1) { break; } } if (x == m + 1) { continue; } f[x] = min(f[x], f[i] + (x - i) - len[j]); } } ans = (ll)(1e18); for (int i = 0; i <= m; i++) { ans = min(ans, 1ll * f[i] + m - i); //f[i]只考虑了前i位,所以后面m - i位需要删除 } printf("%lld\n", ans); return; } int main() { solve(); return 0; } ```