可能更洛谷的阅读体验

题目很好,考察了 SA 中的并查集思想以及启发式合并,而且实现也不难,值得一做。

形式化题面如下:

给定一个长为nn 的字符串,询问次数为qq,多次询问区间[l,r][l,r] 内最长重复子串的长度。

1n5×104,1q1051\le n \le 5\times 10^4,1\le q \le 10^5

没有形式化题面感觉都想不出来怎么做 www。

肯定没有那么菜啦,首先考虑二分长度,问题转化为区间内是否存在一个长为midmid 的最长重复子串。

接下来我们考虑这个最长重复子串怎么求,一个比较明显的想法就是后缀数组的 LCP 功能,原命题询问的实质就是是否存在i,j[l,rmid+1],LCP(i,j)midi,j \in [l,r-mid+1],\operatorname{LCP}(i,j)\ge mid。看到后面这个式子,回忆起品酒大会的思路:从大到小将 Height 数组插入,若仅考虑L\ge L 的 Height,将sai1,saisa_{i-1},sa_{i} 之间连边,那么若p,qp,q 在同一联通块里,表明LCP(p,q)L\operatorname{LCP}(p,q)\ge L。我们通过并查集和启发式合并就可以做到O(logn)O(\log n) 的优秀复杂度啦。

但是有点问题啊,如果我们直接这么做我们并没有考虑区间位置,也就是说在两个联通块启发式合并的时候我们必须要记录区间的位置。我们不妨考虑对于联通块内每一个位置,我们维护它在当前联通块内上一个元素的位置,记作preipre_{i},那么区间限制转化为maxiset(L),i[l,rL+1]preil\max\limits_{i\in set(L),i\in [l,r-L+1]} pre_{i}\ge l。我们可以通过对每一个联通块开主席树来辅助查询,这样就能够做到优秀的O(qlog2nO(q \log^2 n) 的查询啦,其中两个log\log 由二分和主席树查询贡献。

问题转化为如何维护prepre 的合并。首先,唯一确定一个联通块的信息就是所对应的 LCP 长度LL(具体见上面品酒大会思路),根据品酒大会启发式合并的思路,一次启发式prepre 的变化最多只有O(logn)O(\log n) 个,考虑用 set 把联通块内的元素存下来,启发式合并的时候暴力单点修改prepre,这样处理的复杂度是O(nlog2n)O(n \log^2 n) 的,可以过。故总时间复杂度为O(qlog2n+nlog2n)O(q\log^2 n + n \log^2 n)

请注意二分的实现:

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include<bits/stdc++.h>
#define pir pair<int,int>
using namespace std;
constexpr int MN=5e4+15;
int n,q,pre[MN];
vector<int> vht[MN];
set<int> st[MN];
string s;

struct Segment{
#define ls t[p].lson
#define rs t[p].rson

struct Node{
int lson,rson,val;
}t[MN<<9];
int tot,rt[MN];

void pushup(int p){
t[p].val=max(t[ls].val,t[rs].val);
}

void modfiy(int &p,int lst,int l,int r,int pos,int v){
p=++tot;
t[p]=t[lst];
if(l==r){
t[p].val=max(t[p].val,v);
return;
}
int mid=(l+r)>>1;
if(mid>=pos) modfiy(ls,t[lst].lson,l,mid,pos,v);
else modfiy(rs,t[lst].rson,mid+1,r,pos,v);
pushup(p);
}

int query(int p,int l,int r,int fl,int fr){
if(l>=fl&&r<=fr){
return t[p].val;
}
int mid=(l+r)>>1,ret=0;
if(mid>=fl) ret=max(ret,query(ls,l,mid,fl,fr));
if(mid<fr) ret=max(ret,query(rs,mid+1,r,fl,fr));
return ret;
}

#undef ls
#undef rs
}sg;

namespace SA{
int len,sa[MN],x[MN],y[MN],rk[MN],c[MN],ht[MN],ST[30][MN];

// 接受 string 和 vector_int 输入,其他输入不保证正确性
// ST表需要手动初始化调用initst函数
template<typename vct>
void getsa(vct &s){
int m=400000;
len=s.size();
s.insert(s.begin(),' ');
for(int i=1;i<=len;i++){
x[i]=s[i];
++c[x[i]];
}
for(int i=2;i<=m;i++) c[i]+=c[i-1];
for(int i=len;i>=1;i--) sa[c[x[i]]--]=i;
for(int k=1;k<=len;k<<=1){
int num=0;
for(int i=len-k+1;i<=len;i++) y[++num]=i;
for(int i=1;i<=len;i++){
if(sa[i]>k) y[++num]=sa[i]-k;
}
for(int i=1;i<=m;i++) c[i]=0;
for(int i=1;i<=len;i++) c[x[i]]++;
for(int i=2;i<=m;i++) c[i]+=c[i-1];
for(int i=len;i>=1;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);
num=1,x[sa[1]]=1;
for(int i=2;i<=len;i++){
if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]) x[sa[i]]=num;
else x[sa[i]]=++num;
}
if(num==len) break;
m=num;
}
for(int i=1;i<=len;i++) rk[sa[i]]=i;
for(int i=1,k=0;i<=len;i++){
if(rk[i]==1) continue;
if(k) k--;
int j=sa[rk[i]-1];
while(i+k<=len&&j+k<=len&&s[i+k]==s[j+k]) k++;
ht[rk[i]]=ST[0][rk[i]]=k;
}
}
}using namespace SA;

int root(int x){
if(pre[x]==x) return pre[x];
else return pre[x]=root(pre[x]);
}

void merge(int x,int y,int L){
int rx=root(x),ry=root(y);
if(rx==ry) return;
if(st[rx].size()<st[ry].size()) swap(rx,ry);
pre[ry]=rx;
for(auto p:st[ry]){
auto it=st[rx].lower_bound(p);
if(it!=st[rx].end()){
sg.modfiy(sg.rt[L],sg.rt[L],1,n,*it,p);
}
if(it!=st[rx].begin()){
it--;
sg.modfiy(sg.rt[L],sg.rt[L],1,n,p,*it);
}
}
for(auto p:st[ry]) st[rx].insert(p);
}

int main(){
cin>>n>>q>>s;
getsa(s);
for(int i=2;i<=n;i++){
vht[ht[i]].push_back(i);
}
for(int i=1;i<=n;i++){
pre[i]=i;
st[i].insert(i);
}
for(int i=n;i>=1;i--){
sg.rt[i]=sg.rt[i+1];
for(auto p:vht[i]){
merge(sa[p],sa[p-1],i);
}
}
while(q--){
int L,R;
cin>>L>>R;
int l=0,r=R-L+1;
while(l+1<r){
int mid=(l+r)>>1;
if(sg.query(sg.rt[mid],1,n,L,R-mid+1)>=L){
l=mid;
}else r=mid;
}
cout<<l<<'\n';
}
return 0;
}