0%

「BJOI2020」封印

给出只包含小写字母 \(a,b\) 的字符串 \(s,t\)\(q\) 次询问,每次询问 \(s[l,r]\)\(t\) 的最长公共子串长度。\(|s|,|t|,q\leqslant 2\times 10^5\)

首先,我们有一个很经典的套路就是用SA或者SAM求出 \(s\) 每个位置最多能向前扩展多长,使得这个子串是 \(t\) 的子串。

说一下用SAM实现的做法:先把 \(t\) 的自动机建出来,然后让 \(s\)\(t\) 的自动机上匹配。我们设每一位的答案为 \(ans_i\) ,再开一个变量 \(now\) 表示当前在哪个节点。

我们一位一位添加进 \(s\) ,假设本次添加的字符为 \(c\),当 \(now\)\(c\) 的出边时,让 \(ans_i=ans_{i-1}+1\) (注意这里并不是 \(len_{now}+1\) ,因为当前匹配上的串不一定是 \(now\) 节点代表的最长串),然后 \(now=ch[now][c]\)

如果没有,我们沿着后缀链接向上跳 \(fa\) ,直到跳到虚拟节点或者找到了一个有 \(c\) 的出边的节点。

如果跳到了虚拟节点,即 \(t\) 根本就不含 \(c\) 这个字符,那么使 \(ans[i]=0,now=0\)\(0\) 为根节点,\(-1\) 为虚拟节点);

如果找到了 \(c\) 的出边,则 \(ans_i=len_{now}+1,now=ch[now][c]\) (此处的 \(now\) 是你跳到的那个点)。这里为什么是 \(len_{now}+ 1\) 呢?因为我们是从某一个儿子节点跳到这里的,而儿子节点代表的子串都严格比父节点的子串长,所以只要跳一次开始,就包含了这个节点的所有串。这样的总复杂度是线性的,因为匹配长度最多只增加 \(|s|\) 次,所以跳 \(fa\) 的总复杂度也是 \(O(|s|)\) 的。

现在我们有了 \(ans\) 数组,怎么求出真正的答案呢?不难发现答案就是 \(\max\limits_{i=l}^r\min(ans_i,i-l+1)\) ,我们把所有位置按取得 \(\min\) 的是哪一种分成两类,我们发现随着 \(l\) 的增加,会由 \(ans_i\) 取得 \(\min\) 变为 \(i-l+1\) 取得 \(\min\) ,而这个位置为 \(i-ans_i+1\) ,所以我们可以按 \(l\) 离线下来所有询问,开两棵线段树维护两种的答案(单点修改和查询区间 \(\max\)),再给每个位置开个桶,把 \(k-ans_k+1=i\) 的都扔进去,然后当 \(l\) 扫到这个位置就更改就好了。当然你也可以用可持久化线段树来在线处理询问,就是空间多了个 \(\log\) ,代码采用的是离线方法。

代码如下:

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
#include<bits/stdc++.h>
using namespace std;
struct query{int l,r,id;}c[200005];
bool cmp(query x,query y){return x.l<y.l;}
string s,t;
namespace Suffix_Automaton
{
int ch[400005][2],len[400005],fa[400005],tot,last,ans[200005];
void extend(int c)
{
int np=++tot,p=last;
last=tot,len[np]=len[p]+1;
for(;p!=-1&&(!ch[p][c]);p=fa[p])ch[p][c]=np;
if(p==-1)return;
int q=ch[p][c];
if(len[q]==len[p]+1){fa[np]=q;return;}
int nq=++tot;
ch[nq][0]=ch[q][0],ch[nq][1]=ch[q][1];
fa[nq]=fa[q];
len[nq]=len[p]+1;
fa[q]=fa[np]=nq;
for(;p!=-1&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;
}
void get_ans()
{
int now=0;
for(int i=0;i<s.size();i++)
{
int c=s[i]-'a';
if(ch[now][c])now=ch[now][c],ans[i+1]=ans[i]+1;
else
{
while(now&&(!ch[now][c]))now=fa[now];
if(!ch[now][c])ans[i+1]=0;
else ans[i+1]=len[now]+1,now=ch[now][c];
}
}
}
}
struct Segment_Tree
{
int tree[800005];
void modify(int l,int r,int x,int a,int b)
{
if(l==r){tree[x]=b;return;}
int mid=(l+r)>>1;
if(a<=mid)modify(l,mid,x*2,a,b);
else modify(mid+1,r,x*2+1,a,b);
tree[x]=max(tree[x*2],tree[x*2+1]);
}
int query(int l,int r,int x,int a,int b)
{
if(l>=a&&r<=b)return tree[x];
int mid=(l+r)>>1,ans=0;
if(a<=mid)ans=query(l,mid,x*2,a,b);
if(b>mid)ans=max(ans,query(mid+1,r,x*2+1,a,b));
return ans;
}
}t1,t2;
using namespace Suffix_Automaton;
vector<int>v[200005];
int anss[200005];
int main()
{
fa[0]=-1;
cin>>s>>t;
int n=s.size();
for(int i=0;i<t.size();i++)extend(t[i]-'a');
get_ans();
for(int i=1;i<=n;i++)v[i-ans[i]+1].push_back(i),t1.modify(1,n,1,i,ans[i]);
int q;
scanf("%d",&q);
for(int i=1;i<=q;i++)scanf("%d%d",&c[i].l,&c[i].r),c[i].id=i;
sort(c+1,c+q+1,cmp);
int nowl=1;
for(int i=1;i<=q;i++)
{
while(nowl<=c[i].l)
{
for(int j=0;j<v[nowl].size();j++)
t1.modify(1,n,1,v[nowl][j],0),t2.modify(1,n,1,v[nowl][j],v[nowl][j]);
nowl++;
}
anss[c[i].id]=max(t2.query(1,n,1,c[i].l,c[i].r)-c[i].l+1,t1.query(1,n,1,c[i].l,c[i].r));
}
for(int i=1;i<=q;i++)printf("%d\n",anss[i]);
return 0;
}