Description
给出字符串(s,s_0(|s|,|s_0|leq10^5)),求有多少个(s_0)的连续子串修改小于等于三个字母能够变成(s)。共有(T(Tleq10))组测试数据。
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(Tcdot|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
",ans);
}
return 0;
}
P.S.
现在一想好简单啊...
因为要拼起来所以后缀数组大小为2e5。