前言
KMP 与 DP。
题目分析
简化题意
给定字符串 S
和 T
,求在 S
中最少删几个字符才能使 S
中不出现 T
。
思路
看到字符串匹配类型的题目,第一时间想到 KMP 算法。考虑暴力,枚举 S
中的每一个字符删或不删,后使用 KMP 算法匹配。
看一眼数据规模 ,发现 For all test cases, N <= 10000, M <= 1000.
显然刚才的暴力不能满足题目的时限要求。
考虑优化,设动态规划状态为
f
i
,
j
f_{i, j}
fi,j,表示在 S
的
1
1
1 到
i
i
i 位中删掉一些字符后末尾最多能与 T
的
1
1
1 到
j
j
j 位进行配对的最少字符删减数量。
对于动态规划的转移方程,我们可以从两种情况来考虑(对于此处的状态,我们可以发现主动转移比被动转移的代码与思路都容易许多,所以使用主动转移):
-
在转移中保留 S i + 1 S_{i + 1} Si+1: 对于这种情况,我们希望找到 一个数 k k k 使 S 1 ⋯ i + 1 S_{1 \cdots i + 1} S1⋯i+1 与 T 1 ⋯ k T_{1 \cdots k} T1⋯k 配对。在寻找 k k k 的过程中,我们可以使用类似于 KMP 算法的
while
循环来寻找(即利用Bonder
的性质,让已经匹配的部分尽量长)。在这种情况下, f i + 1 , k = f i , j f_{i + 1, k} = f_{i, j} fi+1,k=fi,j(注意当 k k k 为 0 0 0 时也需要转移)。 -
在转移中删除 S i + 1 S_{i + 1} Si+1: 这种情况下匹配的长度与转移前相同,即 f i + 1 , j = f i , j + 1 f_{i + 1, j} = f_{i, j} + 1 fi+1,j=fi,j+1。
参考代码
#include<bits/stdc++.h> //万能头
using namespace std;
const int NR = 1e4;
const int MR = 1e3;
const int KR = 2e2;
char s[NR + 10];
char t[MR + 10];
int p[MR + 10];
int dp[NR + 10][MR + 10]; //dp 数组
int f[NR + 10][KR + 10]; //记忆化
void KMP(char x[], int len){ //预处理 t 的前缀最大 Bonder 长
p[0] = 0;
p[1] = 0;
for(int i = 2;i <= len;i++){
int j = p[i - 1];
while(j != 0 && x[j + 1] != x[i]){
j = p[j];
}
p[i] = (x[j + 1] == x[i] ? j + 1 : 0);
}
return ;
}
int cal(int j, char c){ //记忆化优化计算 k
if(f[j][c] != -1) return f[j][c]; //已经计算过则返回
int k = j; //否则计算
while(k != 0 && c != t[k + 1]){ //利用 Bonder 的性质,让已经匹配的部分尽量长
k = p[k];
}
return f[j][c] = k; //计算完毕,返回
}
int main(){
//初始化
memset(dp, 0x3f3f3f3f, sizeof(dp));
memset(f, -1, sizeof(f));
dp[0][0] = 0;
//读入
scanf("%s%s", s + 1, t + 1);
int n = strlen(s + 1), m = strlen(t + 1);
//预处理
KMP(t, m);
//动态规划
for(int i = 0;i < n;i++){
for(int j = 0;j < m;j++){
int k = cal(j, s[i + 1]); //计算 k
// 转移 1
if(s[i + 1] == t[k + 1] && k + 1 != m){
dp[i + 1][k + 1] = min(dp[i + 1][k + 1], dp[i][j]);
}
else if(k + 1 != m) dp[i + 1][0] = min(dp[i + 1][0], dp[i][j]); //注意当 $k$ 为 $0$ 时也需要转移
dp[i + 1][j] = min(dp[i + 1][j], dp[i][j] + 1); //转移 2
}
}
//统计答案
int Min = INT_MAX;
for(int i = 0;i < m;i++){
Min = min(Min, dp[n][i]);
}
printf("%d\n", Min); //输出
return 0;
}