[子序列自动机][动态规划] HDU 6774 String Distance

2020 Multi-University Training Contest 2 (1012)

题目大意

给你两个字符串 \(A,B\), \(|A|\leq 10^5,|B|\leq 20\),每次询问 \(A\) 串的一个子串 \(A_L...A_R\),问该子串和 \(B\) 的最长公共子序列的长度(其实要求的是一个东西减去LCS,但无关紧要)。

题解

可以使用子序列自动机+dp。子序列自动机其实就是一个 \(Next\) 数组,\(Next[i][c]\) 表示字符串在第 \(i\) 个字符之后(不包括 \(i\) )出现的第一个字符 \(c\) 的位置(这也配叫自动机?)。

那么 \(Next\) 数组其实非常好求,for一下就可以了。

for(int i=N;i>=1;--i){
    for(int j=0;j<26;++j)
        Next[i-1][j]=Next[i][j];
    Next[i-1][str[i]-'a']=i;
}

\(dp[i][j]\) 表示 \(B\) 的前 \(i\) 个字符和 \(A_L...A_R\) 的某个前缀的公共子序列长度为 \(j\) 时,这个最短前缀的长度。

那么有 \(dp[i][j]=\min\{dp[i][j],dp[i-1][j]\}\),
\(dp[i][j]=\min\{dp[i][j],Next[dp[i-1][j-1]][B[i]]\}\)

因为 \(B\) 的长度只有20,所以对于每个询问,我们重新dp一次。
预处理出 \(Next\) 数组的时间复杂度为 \(O(26|A|)\), 每次dp的复杂度为 \(O(|B|^2)\),因此,对于 \(m\) 个询问,这道题的时间复杂度为 \(O(26|A|+m|B|^2)\)

想了半天怎么分块,怎么莫队,结果一个dp就完事了,这种题还是写得少= =

Code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <vector>
using namespace std;

#define RG register int
#define LL long long

template<typename elemType>
inline void Read(elemType &T){
    elemType X=0,w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    T=(w?-X:X);
}

int Next[100010][26],dp[21][26];
char str[100010],pat[30];
int T,N,M,Q;

inline void Init(){
    memset(Next,0x3f,sizeof(Next));
    for(RG i=N;i>=1;--i){
        for(RG j=0;j<26;++j)
            Next[i-1][j]=Next[i][j];
        Next[i-1][str[i]-'a']=i;
    }
    return;
}

inline int Solve(int L,int R){
    memset(dp,0x3f,sizeof(dp));
    dp[0][0]=L-1;
    for(RG i=1;i<=M;++i){
        dp[i][0]=L-1;
        for(RG j=1;j<=i;++j){
            dp[i][j]=min(dp[i][j],dp[i-1][j]);
            if(dp[i-1][j-1]<R)
                dp[i][j]=min(dp[i][j],Next[dp[i-1][j-1]][pat[i]-'a']);
        }
    }
    for(RG i=M;i>=0;--i)
        for(RG j=i;j<=M;++j)
            if(dp[j][i]<=R) return i;
    return 0;
}

int main(){
    Read(T);
    while(T--){
        scanf("%s",str+1);
        scanf("%s",pat+1);
        N=strlen(str+1);
        M=strlen(pat+1);
        Init();
        Read(Q);
        while(Q--){
            int L,R;
            Read(L);Read(R);
            printf("%d\n",R-L+1+M-(Solve(L,R)<<1));
        }
    }
    return 0;
}
posted @ 2020-07-23 22:42  AE酱  阅读(427)  评论(1编辑  收藏  举报