题目链接:点击查看
题目大意:给出两个字符串 A 和 B,规定两个字符串的距离 dis( s , t ) 为,可以在字符串 s 或 t 的任意位置添加或删除字符,使得两个字符串相等的最小操作次数,现在给出 q 次询问,每次询问给出字符串 A 的一个子串,问 dis( A[ l : r ] , B ) 是多少
题目分析:首先简化题目中的 dis 函数,假设 | s | < | t | ,再设 lcs 为其最长公共子序列的长度,我们可以将字符串 s 和 t 都变为 lcs,这样显然就相等了,将其转换为 lcs 只需要将多余的字母删除即可,操作数为 | s | - | t | - 2 * lcs
这样一来,对于每次询问,只需要求出 A 的子串与 B 串的 lcs 就好了,但是 A 的长度能达到 1e5 ,普通的 n * m 的求解方法肯定是不行的了,但是又考虑 B 的长度只有 20 ,所以可以考虑序列自动机优化,将求解 lcs 的时间复杂度下降到 m * m
具体优化方式如下,首先对 A 串建立序列自动机,nt[ i ][ j ] 表示从第 i 个位置往后(包含),首次出现字母 j 的位置,然后对于 B 串dp[ i ][ j ] 代表到第 i 位为止,lcs 为 j 时在 A 串最早结束的位置,这样 dp[ i ][ j ] 的转移无非只能从 dp[ i - 1 ][ j ] 和 dp[ i - 1 ][ j - 1 ] 的位置后找到首次出现 b[ i ] 的位置,取个最小值就好了,因为 dp 数组是可以迭代更新的,所以可以省去第一维
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=1e5+100;
char a[N],b[N];
int nt[N][26],dp[25];//dp[i][j]:到第i位为止,lcs为j时的最早结束位置
int main()
{
#ifndef ONLINE_JUDGE
// freopen("data.in.txt","r",stdin);
// freopen("data.out.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
int w;
cin>>w;
while(w--)
{
scanf("%s%s",a+1,b+1);
int n=strlen(a+1),m=strlen(b+1);
for(int i=0;i<26;i++)
nt[n+1][i]=n+1;
for(int i=n;i>=0;i--)
{
for(int j=0;j<26;j++)
nt[i][j]=nt[i+1][j];
if(i>0)
nt[i][a[i]-'a']=i;
}
int q;
scanf("%d",&q);
while(q--)
{
int l,r;
scanf("%d%d",&l,&r);
int lcs=0;
memset(dp,0x3f,sizeof(dp));
dp[0]=l-1;
for(int i=1;i<=m;i++)
for(int j=m;j>=1;j--)
{
if(dp[j-1]<=r&&nt[dp[j-1]+1][b[i]-'a']<=r)
dp[j]=min(dp[j],nt[dp[j-1]+1][b[i]-'a']);
if(dp[j]<=r)
lcs=max(lcs,j);
}
printf("%d\n",r-l+1+m-2*lcs);
}
}
return 0;
}