题意:给出两个字符串a(1e5)和b(20),再给出q个询问区间l,r,求字符串a下标l到r的子串变为b最少要几次操作,每次操作限定为插入或删除一个字符。
题解:序列自动机+dp
从一个字符串
a
[
l
,
r
]
a[l, r]
a[l,r]变为另一个字符串
b
b
b,我们可以先求得两串的
l
c
s
lcs
lcs,先删去
a
a
a中不是
l
c
s
lcs
lcs的,共
r
−
l
+
1
−
l
c
s
r-l+1-lcs
r−l+1−lcs,再补上使得与
b
b
b相等,共
m
−
l
c
s
m-lcs
m−lcs,所以一共是
r
−
l
+
1
+
m
−
2
∗
l
c
s
r-l+1+m-2*lcs
r−l+1+m−2∗lcs。
接下来考虑如何以较短时间求
l
c
s
lcs
lcs。
正常求法肯定不行,我们可以发现
b
b
b的长度只有20,那么
l
c
s
lcs
lcs最多也就20。
于是我们可以设
d
p
[
i
]
[
l
e
n
]
=
j
dp[i][len]=j
dp[i][len]=j,表示
a
[
l
,
j
]
a[l,j]
a[l,j]与
b
[
1
,
i
]
b[1,i]
b[1,i]的
l
c
s
lcs
lcs长度为
l
e
n
len
len。
但是这样做必须要先预处理出 a a a串从位置 i i i开始某个指定字符的最近位置,我们用序列自动机去求就好了, n x t [ i ] [ j ] nxt[i][j] nxt[i][j]表示从 i i i开始字符 j j j的最近位置。
那么我们就可以得到转移方程:
dp[i][j] = min(dp[i][j], nxt[dp[i - 1][j - 1] + 1][b[i] - 97]);
然后只要dp值即右端点在r内就是lcs。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#include<fstream>
#include<set>
#include<map>
#include<sstream>
#include<iomanip>
#define ll long long
using namespace std;
const int maxn = 1e5 + 5;
char a[maxn], b[22];
int t, q, l, r, la, lb, nxt[maxn][26], dp[22][22];
int main() {
scanf("%d", &t);
while (t--) {
scanf("%s%s", a + 1, b + 1);
la = strlen(a + 1);
lb = strlen(b + 1);
for (int i = 0; i < 26; i++) nxt[la + 1][i] = la + 1;
for (int i = la; i >= 1; i--) {
for (int j = 0; j < 26; j++) nxt[i][j] = nxt[i + 1][j];
nxt[i][a[i] - 97] = i;
}
scanf("%d", &q);
while (q--) {
scanf("%d%d", &l, &r);
for (int i = 1; i <= lb; i++) dp[0][i] = la + 1; //不存在
dp[0][0] = l - 1; //初始位置
for (int i = 1; i <= lb; i++) {
dp[i][0] = l - 1; //不存在
for (int j = 1; j <= lb; j++) {
dp[i][j] = dp[i - 1][j];
if (dp[i - 1][j - 1] < r)
dp[i][j] = min(dp[i][j], nxt[dp[i - 1][j - 1] + 1][b[i] - 97]);
}
}
int lcs = 0;
for (int i = lb; i >= 1; i--) {
if (dp[lb][i] <= r) {
lcs = i;
break;
}
}
printf("%d\n", r - l + 1 + lb - 2 * lcs);
}
}
return 0;
}