洛谷P3763 - [TJOI2017]DNA
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。