隐藏
「BJOI2015」隐身术 - 后缀数组+倍增+动态规划 | Bill Yang's Blog

路终会有尽头,但视野总能看到更远的地方。

0%

「BJOI2015」隐身术 - 后缀数组+倍增+动态规划

题目大意

    给定两个串A,B。请问B中有多少个非空子串和A的编辑距离不超过$K$?
    所谓“子串”,指的是B中连续的一段。不同位置的内容相同的子串算作多个。两个串之间的“编辑距离”指的是把一个串变成另一个串需要的最小的操作次数,每次操作可以插入、删除或者替换一个字符。


题目分析

设$f[i,j,k]$为考虑$A$串$i$的后缀,$B$串$j$的后缀,已经使用$k$次编辑时合法的答案个数。

使用两个后缀的LCP来跳过一段相同字符。
LCP可以使用后缀数组+ST表来求。

当跳到了相同字符的末尾处,只能通过编辑B串来继续转移了。
故$(i,j,k)$可以转移到$(i+1,j,k+1),(i,j+1,k+1),(i+1,j+1,k+1)$。

因为卡常,所以将终点枚举放到Dfs中进行,同时用一些奇怪的技巧优化掉。


代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<climits>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>

using namespace std;

inline const int Get_Int() {
int num=0,bj=1;
char x=getchar();
while(!isdigit(x)) {
if(x=='-')bj=-1;
x=getchar();
}
while(isdigit(x)) {
num=num*10+x-'0';
x=getchar();
}
return num*bj;
}

const int maxn=200005,K=20;

int sa[maxn],Rank[maxn],First[maxn],Second[maxn],Bucket[maxn],tmp[maxn],Height[maxn],Log2[maxn];

void Suffix_Array(int n,int* a) {
fill(Bucket,Bucket+n+1,0);
for(int i=1; i<=n; i++)Bucket[a[i]]++;
for(int i=1; i<=n; i++)Bucket[i]+=Bucket[i-1];
for(int i=1; i<=n; i++)Rank[i]=Bucket[a[i]-1]+1;
for(int t=1; t<=n; t*=2) {
for(int i=1; i<=n; i++) {
First[i]=Rank[i];
Second[i]=(i+t)>n?0:Rank[i+t];
}
fill(Bucket,Bucket+n+1,0);
for(int i=1; i<=n; i++)Bucket[Second[i]]++;
for(int i=1; i<=n; i++)Bucket[i]+=Bucket[i-1];
for(int i=1; i<=n; i++)tmp[n-(--Bucket[Second[i]])]=i;
fill(Bucket,Bucket+n+1,0);
for(int i=1; i<=n; i++)Bucket[First[i]]++;
for(int i=1; i<=n; i++)Bucket[i]+=Bucket[i-1];
for(int i=1; i<=n; i++)sa[Bucket[First[tmp[i]]]--]=tmp[i];
bool unique=true;
for(int j=1,last=0; j<=n; j++) {
int i=sa[j];
if(!last)Rank[i]=1;
else if(First[i]==First[last]&&Second[i]==Second[last])Rank[i]=Rank[last],unique=false;
else Rank[i]=Rank[last]+1;
last=i;
}
if(unique)break;
}
int k=0;
for(int i=1; i<=n; i++) {
if(Rank[i]==1)k=0;
else {
if(k>0)k--;
int j=sa[Rank[i]-1];
while(i+k<=n&&j+k<=n&&a[i+k]==a[j+k])k++;
}
Height[Rank[i]]=k;
}
}

int n,m,k,a[maxn],b[maxn],f[maxn][K],vst[maxn],pos,ans=0;
char A[maxn],B[maxn];

int LCP(int x,int y) {
x=Rank[x],y=Rank[y+n+1];
if(x>y)swap(x,y);
x++;
int k=Log2[y-x+1];
return min(f[x][k],f[y-(1<<k)+1][k]);
}

void Dfs(int x,int y,int z) {
if(x>n&&y<=m+1&&y>pos&&vst[y]!=pos)vst[y]=pos,ans++;
int lcp=LCP(x,y);
x+=lcp,y+=lcp;
for(int i=0; i<=min(k-z-(n-x+1),lcp-1); i++)
if(vst[y-i]!=pos&&y-i>pos)vst[y-i]=pos,ans++;
if(z==k)return;
if(x<=n)Dfs(x+1,y,z+1);
if(y<=m)Dfs(x,y+1,z+1);
if(x<=n&&y<=m)Dfs(x+1,y+1,z+1);
}

int main() {
k=Get_Int();
scanf("%s%s",A+1,B+1);
n=strlen(A+1),m=strlen(B+1);
int cnt=0;
for(int i=1; i<=n; i++)a[++cnt]=A[i];
a[++cnt]='#';
for(int i=1; i<=m; i++)a[++cnt]=B[i];
copy(a+1,a+cnt+1,b+1);
sort(a+1,a+cnt+1);
int tot=unique(a+1,a+cnt+1)-a-1;
for(int i=1; i<=cnt; i++)b[i]=lower_bound(a+1,a+tot+1,b[i])-a;
Suffix_Array(cnt,b);
Log2[1]=0;
for(int i=2; i<=cnt; i++)Log2[i]=Log2[i>>1]+1;
for(int i=1; i<=cnt; i++)f[i][0]=Height[i];
for(int j=1; j<K; j++)
for(int i=1; i+(1<<(j-1))<=cnt; i++)
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
for(pos=1; pos<=m; pos++)Dfs(1,pos,0);
printf("%d\n",ans);
return 0;
}
姥爷们赏瓶冰阔落吧~