鉴于我实在不是很懂单调栈和单调队列这一系列东西,所以我决定稍微具体讲一下单调栈。
恩,本题实质上就是求两个字符串的公共子串数,其中只要出现位置不同,就算是不同的子串。
处理多个字符串的经典套路:把两个字符串连在一起,中间用分割符分割。
于是问题就转化为了:求分隔符前后都出现过的子串个数。
子串就是后缀的前缀,于是问题又转化成了:求整个串中,任意两个后缀的LCP之和,这两个子串要一个在分割符前,一个在分割符后。
恩,求后缀的LCP我们可以用后缀数组中的height数组,两个后缀的LCP长度就是height上的区间最小值。
于是我们要求的实际上就是:
其中SA是排名第
i
i
的后缀的编号。
让我们姑且只考虑左边,一个明显的暴力解法是枚举然后RMQ求出区间最小值。
然而这个解法明显不符合时间复杂度的要求,因此我们的方法有待改进。
首先我们发现,右端点固定的一系列区间最小值,能够构成一个单调增的序列,其中相同的值可以合并。
如果我们不断将右端点右移,则序列末端的一些值会被更新为同一个值,可以将这几个值合并,相当于这几个值被从队列末端弹掉了。
与此同时如果你又扫到了某一个
SA[i]<n+1
S
A
[
i
]
<
n
+
1
的
i
i
,你又要把它加进序列的末端。
这相当于一个栈的结构,而其中的元素又是单调的,就是所谓的单调栈。
于是,我们可以使用单调栈,求出式子的值即可,时间复杂度为,加上构造后缀数组,共 O(nlogn) O ( n l o g n ) 。
诸君,我讨厌单调栈。
讲真,一开始,我的解法是二分,然后二分合并左右两边结果的时候用的又是单调栈,然后还AC了,后来看题解才发现,只用单调栈就可以了。
具体详见代码如下:
// luogu-judger-enable-o2
#include <bits/stdc++.h>
#define LL long long
using namespace std;
int n,len,SA[400010],r[400010],tp[400010],t[400010],h[400010],st[400010],s[400010],top;
char ch[400010];
void Sort(int m) {
memset(t,0,sizeof(int)*(m+1));
for(int i=1; i<=len; i++) {
t[r[i]]++;
}
for(int i=1; i<=m; i++) {
t[i]+=t[i-1];
}
for(int i=len; i; i--) {
SA[t[r[tp[i]]]--]=tp[i];
}
}
void build_SA() {
Sort(127);
for(int i=1,p=0,m=127; i<=len&&p<len; i<<=1,m=p) {
p=0;
for(int j=len-i+1; j<=len; j++) {
tp[++p]=j;
}
for(int j=1; j<=len; j++) {
if(SA[j]>i) {
tp[++p]=SA[j]-i;
}
}
Sort(m);
swap(r,tp);
r[SA[1]]=p=1;
for(int j=2; j<=len; j++) {
r[SA[j]]=((tp[SA[j]]==tp[SA[j-1]])&&(tp[SA[j]+i]==tp[SA[j-1]+i])?p:++p);
}
}
for(int i=1,j,k=0; i<=len; h[r[i++]]=k) {
for(k?k--:0,j=SA[r[i]-1]; ch[i+k]==ch[j+k]; k++);
}
}
LL solve(bool b) {
LL ret=0,temp=0;
top=0;
for(int i=len,j=0; i; i--,j=0) {
if((SA[i]>n)^b){
ret+=temp;
}
while(st[top]>h[i]&&top)
{
temp-=1LL*s[top]*st[top];
j+=s[top--];
}
st[++top]=h[i];
s[top]=j;
if((SA[i]<=n)^b) { //向单调栈内插入元素
s[top]++;
}
temp+=1LL*s[top]*st[top];
}
return ret;
}
int main() {
scanf("%s",ch+1);
n=strlen(ch+1);
ch[n+1]='$';
scanf("%s",ch+n+2);
len=strlen(ch+1);
for(int i=1;i<=len;i++)
{
tp[i]=i;
r[i]=ch[i];
}
build_SA();
cout<<solve(0)+solve(1)<<endl;
return 0;
}