洛谷P3763 - [TJOI2017]DNA

Portal

Description

给出字符串\(s,s_0(|s|,|s_0|\leq10^5)\),求有多少个\(s_0\)的连续子串修改小于等于三个字母能够变成\(s\)。共有\(T(T\leq10)\)组测试数据。

Solution

后缀数组。
易知\(s_0\)\(|s_0|-|s|+1\)个长度为\(|s|\)的子串,我们依次检查这些子串是否合法。
检查从位置\(i\)开始的子串是否合法时,设已经匹配了\(j\)位,那么求出\(s_0\)\(i+j\)位与\(s\)\(j+1\)位的最长公共前缀\(len\),说明他们匹配\(len\)位后失配。那么用掉一次修改机会跳过这一位,即\(j=j+len\),继续进行匹配。如果三次机会都用掉了依然有\(j<|s|\),说明不合法,否则合法。求LCP可以将\(s_0\)\(s\)拼起来再求后缀数组来\(O(1)\)得到。

时间复杂度\(O(T\cdot|s|log|s|)\)

Code

//[TJOI2017]DNA
#include <algorithm>
#include <cstdio>
#include <cstring>
using std::min; using std::swap;
const int N=2e5+10;
int n,m; char s0[N],s[N];
int sa[N],rnk[N<<1],h[N];
int cnt[N],tmp[N],rnk1[N];
int Lg2[N],rmq[N][20];
void getSA(char s[],int n)
{
    memset(cnt,0,sizeof cnt);
    for(int i=1;i<=n;i++) cnt[s[i]]=1;
    for(int i=1;i<=256;i++) cnt[i]+=cnt[i-1];
    for(int i=1;i<=n;i++) rnk[i]=cnt[s[i]];
    for(int L=1;L<=n;L<<=1)
    {
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++) cnt[rnk[i+L]]++;
        for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--) tmp[cnt[rnk[i+L]]--]=i;
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++) cnt[rnk[tmp[i]]]++;
        for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--) sa[cnt[rnk[tmp[i]]]--]=tmp[i];
        int k=0;
        for(int i=1;i<=n;i++)
        {
            if(rnk[sa[i]]!=rnk[sa[i-1]]||rnk[sa[i]+L]!=rnk[sa[i-1]+L]) k++;
            rnk1[sa[i]]=k;
        }
        memcpy(rnk,rnk1,sizeof rnk1);
        if(k>=n) break;
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rnk[i]==1) {h[1]=k=0; continue;}
        if(k>0) k--;
        while(s[i+k]==s[sa[rnk[i]-1]+k]) k++;
        h[rnk[i]]=k;
    }
    for(int i=1;i<=n;i++) rmq[i][0]=h[i];
    for(int i=2;i<=n;i++) Lg2[i]=Lg2[i>>1]+1;
    for(int k=1;(1<<k)<=n;k++)
        for(int i=1;i+(1<<k)-1<=n;i++) rmq[i][k]=min(rmq[i][k-1],rmq[i+(1<<k-1)][k-1]);
}
int lcp(int x,int y)
{
    int i=rnk[x],j=rnk[y];
    if(i>j) swap(i,j);
    int t=Lg2[j-i];
    return min(rmq[i+1][t],rmq[j-(1<<t)+1][t]);
}
int main()
{
    int task; scanf("%d",&task);
    while(task--)
    {
    
    scanf("%s%s",s0+1,s+1);
    n=strlen(s0+1),m=strlen(s+1);
    s0[n+1]='#';
    for(int i=1;i<=m;i++) s0[n+1+i]=s[i];
    getSA(s0,n+m+1);
    int ans=0;
    for(int i=1;i<=n-m+1;i++)
    {
        int j=0;
        for(int k=0;j<=m&&k<=3;j++,k++) j+=lcp(i+j,n+2+j);
        if(j>m) ans++;
    }
    printf("%d\n",ans);

    }
    return 0;
}

P.S.

现在一想好简单啊...
因为要拼起来所以后缀数组大小为2e5。

posted @ 2018-04-23 11:27  VisJiao  阅读(218)  评论(0编辑  收藏  举报