洛谷 P3082 思路分享(kmp,dp)
https://www.luogu.com.cn/problem/P3082
题意概述
给定两字符串 \(s,t\),长度分别为 \(n,m\).
通过移除 \(s\) 中若干字符,让 \(s\) 的任意一个子串中都不包含 \(t\),求最小移除的字符数.
\(1\le n \le 10^4\),\(1\le m \le 10^3\).
思路
将求最小移除字符数转化成求最大能保留的长度.
考虑 \(dp\),令 \(dp[i][j]\) 为处理到 \(s\) 的 \(i\) 个字符,保留的字符串后缀匹配了 \(t\) 的前 \(j\) 个字符,能保留的最大长度.
转移考虑两种情况:
-
移除 \(s[i+1]\),\(dp[i+1][j] = \max(dp[i+1][j],dp[i][j])\).
-
保留 \(s[i+1]\),此时要得到加上 \(s[i+1]\) 后与 \(t\) 的匹配情况,这和 \(kmp\) 的逻辑一样,如果失配就根据 \(next\) 往前跳,可以做一下类似路径压缩的预处理. 定义 \(f[j][c]\) 为当前匹配了 \(t\) 的前 \(j\) 个字符,下一个字符为 \(c\),加上 \(c\) 后匹配的情况,那么转移方程为:
\[dp[i+1][f[j][s[i]-'a']] = \max(dp[i+1][f[j][s[i]-'a']],dp[i][j]+1)
\]
这里字符串是 \(0-based\).
\(f\) 可以递推求解:
\[f[i][c] =
\begin{cases}
i+1,t[i]-'a' = c \\[1em]
f[next[i]][c],otherwise
\end{cases}
\]
最终答案为 \(n-\max(dp[n][0],\cdots,dp[n][m-1])\).
时间复杂度 \(\mathcal{O}(nm)\).
代码
//author:kzssCCC
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int INF = 1e9;
void solve(){
string s,t;
cin >> s >> t;
int n = s.size();
int m = t.size();
vector<int> next(m+1);
int cmp=0,cur=2;
while (cur<=m){
if (t[cmp]==t[cur-1]){
next[cur] = cmp+1;
cmp++,cur++;
}
else if (cmp){
cmp = next[cmp];
}
else{
cur++;
}
}
vector<vector<int>> f(m,vector<int>(26));
for (int i=0;i<m;i++){
for (int c=0;c<26;c++){
if (t[i]-'a'==c){
f[i][c] = i+1;
}
else{
f[i][c] = f[next[i]][c];
}
}
}
vector<int> dp(m+1,-INF);
dp[0] = 0;
for (int i=0;i<n;i++){
auto ndp = dp;
for (int j=0;j<m;j++){
if (dp[j]==-INF) continue;
int k = f[j][s[i]-'a'];
ndp[k] = max(ndp[k],dp[j]+1);
}
dp = ndp;
}
int mx = -INF;
for (int i=0;i<m;i++){
mx = max(mx,dp[i]);
}
cout << n-mx << '\n';
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}

浙公网安备 33010602011771号