题意:给两个字符串一个S串,一个T串。
每次查询,在S[L,L+1…R] 和T[1,2,…m] ( m为T的长度)中进行操作。
操作一:可以在任意一个字符串的任意一个位置加任意一个字符
操作二:可以在删除任意一个字符串内的任意一个字符。
问最少要操作多少次,能让两个字符串相等。
思路:
1、容易发现,操作一的效果没啥用,效果等价操作二。
对一个字符串的一个位置加上字符,显然是想要让这个位置与另一个字符串该位置的字符一样。那么我直接把另一个字符串的这个位置的字符删掉即可。
那么问题等价找S[L,L+1…R] 和T[1,2,…m] 的LCS(最长公共子序列)
答案就是R - L + 1 + m - 2 * LCS
2、如果直接求LCS,复杂度是O(NM) 显然是会超时的。
这里考虑用dp[i][j] 表示对于字符串T的前i位与S串的LCS长度为j时,S串最后一位的最靠前的位置。
这里考虑用序列自动机优化,Nex[i][j]表示S串的第i位的下一个字符为j时的最近的一个位置
3、那么可以得到转移方程为
dp[i][j]=min(dp[i][j],dp[i-1][j]) 意思是对于T串的前i位,LCS长度为j时与前i-1位长度LCS长度为j时取一个最小值。
其实直接写dp[i][j]=dp[i-1][j]即可,因为是第一次到dp[i][j]这个状态,所以只需要看看对于前i-1个字符是不是能形成LCS长度为j
dp[i][j]=min(dp[i][j],Nex[dp[i-1][j-1]][t[i]-‘a’]
这个意思是,当LCS长度为j-1时候,找到在T串的位置,然后找到离这个位置最近的下一个字符为t[i]的位置
其实这两个方程的意思就是说,选不选当前位置作为LCS的最后一个字符。
第一个方程就是说不选t[i]时候,LCS为j时候,在S串中的最靠前的下标。
第二个方程就是说选t[i]作为LCS结尾的字符的时候,在S串中的最靠前的下标
4、注意边界条件。
对于dp[i][0] 需要赋值为l-1,因为对于S串的区间是在[L,R] 那么上一个位置就是L-1。
对于其他的赋值为len(S)+1。
只需要找到最大的j,满足dp[i][j]<=R即可,j就是lcs
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int Nex[N][26],dp[25][25];//dp[i][j]表示t串前i个字符和s串的lcs为j时候 在s串的最小下标
int main(){
int T;cin>>T;
while(T--){
string s,t;cin>>s>>t;
s=" "+s;
t=" "+t;
int lenA=s.size()-1,lenB=t.size()-1;
for(int i=0;i<26;i++) Nex[lenA][i]=0;
for(int i=lenA;i;i--){
for(int j=0;j<26;j++) Nex[i-1][j]=Nex[i][j];
Nex[i-1][s[i]-'a']=i;
}
int Q;cin>>Q;
while(Q--){
int l,r;cin>>l>>r;
int lcs=0;
for(int i=0;i<=lenB;i++){
dp[i][0]=l-1;
for(int j=1;j<=lenB;j++) dp[i][j]=lenA+1;
}
for(int i=1;i<=lenB;i++){
for(int j=1;j<=i;j++){
dp[i][j]=dp[i-1][j];///不选t[i]作为lcs的最后一个字符
if(Nex[dp[i-1][j-1]][t[i]-'a']) {
///选t[i]作为lcs的最后一个字符
dp[i][j]=min(dp[i][j],Nex[dp[i-1][j-1]][t[i]-'a']);
}
if(dp[i][j]<=r) lcs=max(lcs,j);
}
}
cout<<r-l+1+lenB-2*lcs<<endl;
}
}
return 0;
}