题目大意:给了1个长度为n(1<=n<=1e5)的字符串A,和一个长度为m(1<=m<=20)的字符串B,有q(1<=q<=1e5)次询问,每次询问A[ l,...,r ]与B[ 1,...,m ]的“距离”。很显然,距离 = r - l + 1 - LCS (A[ l,...,r ] , B).
那么,我们现在的问题就转变成了如何快速地求得两字符串的LCS
题解思路:普通的LCS dp式是O(n*m)的,由于这题n很大,所以这种算法必然会超时,那么我们能否转换一下呢?我们注意到m很小,如果能只对B串内的元素进行dp,那么时间复杂度无疑会大大地降低。
朴素想法:开26个vector从小到大存下a串中每个元素的位置,dp[i][j] 代表的是在只考虑b[1,...,i]的情况下,拥有长度为 j 的LCS所需A串长度最小值(以 left 为起点),那么dp方程该如何转移呢?首先,假设我们已经知道 dp[i-1][j-1],如果我们需要将B[i]元素加入到LCS中,那么我们就需要在dp[i-1][j-1]这个位置的后面去寻找 B[i] 最小的位置p,然后 dp[i][j] = p。那么p怎么找呢?前面我们已经用vector处理出了每个元素的位置,二分查找一下即可,p=upper_bound ( G[B[i]-'a'].begin() , G[B[i]-'a'].end() ,dp[i-1][j-1] ),注意p不能超过右边界的范围。这样其实我们的dp转移就已经完成了。
然而,很尴尬地发现这样会TLE,因为dp是m2的,再加上个二分查找的log,就不可避免地 t 了
那么能不能把这个二分去掉呢?
优化想法:直接利用数组预处理出在[i,...,n]区间内每一个最先出现的字母的位置,g[i][j] 表示 A[i..n] 里字符 j 最早出现的下标。这种预处理操作叫做序列自动机。这样我们就不需要每次都去二分查找dp[i-1][j-1]这个位置的后面去寻找 B[i] 最小的位置p,这个位置p我们事先已经预处理出来了p=g[ dp[i-1][j-1] ][ B[i]-'a' ]。这样,算法就完成了。
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> PII; #define ls l,mid,rt<<1 #define rs mid+1,r,rt<<1|1 #define endl ' ' #define p4 puts("444") const int MAXN = 1e6+10; const double Pi = acos(-1.0); const double EPS = 1e-8; const ll mod = 1e9+7; int n,m; int f[MAXN][26],dp[25][25]; char a[MAXN],b[MAXN]; void solve(){ scanf("%s",a+1);n=strlen(a+1); scanf("%s",b+1);m=strlen(b+1); memset(f,0,sizeof(f)); for(int i=0;i<26;i++)f[n][i]=1e9; for(int i=n;i>=1;i--){ for(int j=0;j<26;j++)f[i-1][j]=f[i][j]; f[i-1][a[i]-'a']=i; } int l,r,q; scanf("%d",&q); while(q--){ scanf("%d %d",&l,&r); memset(dp,0x3f3f3f3f,sizeof(dp)); dp[0][0]=l-1; for(int i=1;i<=m;i++){ dp[i][0]=l-1; for(int j=1;j<=i;j++){ dp[i][j]=dp[i-1][j]; if(dp[i-1][j-1]<=r)dp[i][j]=min(dp[i][j],f[dp[i-1][j-1]][b[i]-'a']); } } int ans=r-l+1+m; for(int i=m;i>=0;i--){ if(dp[m][i]<=r&&dp[m][i]>=l){ ans-=2*i; break; } } cout<<ans<<endl; } } int main() { int T=1; scanf("%d",&T); while(T--)solve(); }