「kuangbin带你飞」专题十八 后缀数组

传送门

倍增法

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
struct DA{
bool cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
int t1[maxn],t2[maxn],c[maxn];
int rank[maxn],height[maxn],RMQ[maxn],mm[maxn];
int best[20][maxn];
int r[maxn];
int sa[maxn];
int str[maxn];
void da(int n,int m){
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=str[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1){
p=0;
for(i=n-j;i<n;i++)y[p++]=i;
for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n)break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
void initRMQ(int n){
for(int i=1;i<=n;i++)RMQ[i]=height[i];
mm[0]=-1;
for(int i=1;i<=n;i++)
mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
for(int i=1;i<=n;i++)best[0][i]=i;
for(int i=1;i<=mm[n];i++)
for(int j=1;j+(1<<i)-1<=n;j++){
int a=best[i-1][j];
int b=best[i-1][j+(1<<(i-1))];
if(RMQ[a]<RMQ[b])best[i][j]=a;
else best[i][j]=b;
}
}
int askRMQ(int a,int b){
int t;
t=mm[b-a+1];
b-=(1<<t)-1;
a=best[t][a];b=best[t][b];
return RMQ[a]<RMQ[b]?a:b;
}
int lcp(int a,int b){
a=rank[a];b=rank[b];
if(a>b)swap(a,b);
return height[askRMQ(a+1,b)];
}
void print(int n){
cout<<"sa[] ";
for(int i=0;i<=n;i++)cout<<sa[i]<<" ";cout<<endl;
cout<<"rank[] ";
for(int i=0;i<=n;i++)cout<<rank[i]<<" ";cout<<endl;
cout<<"height[] ";
for(int i=0;i<=n;i++)cout<<height[i]<<" ";cout<<endl;
}
}AA,BB;

A.POJ1743 Musical Theme

题意

有N(1<=N<=20000)个音符的序列来表示一首乐曲,每个音符都是1..88范围内的整数,现在要找一个重复的子串,它需要满足如下条件:1.长度至少为5个音符。
2.在乐曲中重复出现(就是出现过至少两次)。(可能经过转调,“转调”的意思是主题序列中每个音符都被加上或减去了同一个整数值)
3.重复出现的同一主题不能有公共部分。

思路

对于第一二点:至少长度是五,我们可以二分0-n/2之间的长度来查找height(height[i]=排名为i和i-1的后缀子串的最长公共前缀长度缀)然后找到两个大于二分的值,然后判断起点距离是否大于二分的值(因为要求不重叠)</br>
第三点 因为共同加上一个值,但是他们前后的差值还是一样的(例如1,2,3和5,6,7是一样的他们的差值是1)然后把原数组变成一个长度为N-1的数组去处理,因为处理后的数组比原数组少1所以答案也要-1;

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
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=2e5;
const ll inf=0x3f3f3f3f3f3f;
#define bug cout<<"here:"<<endl;
int sa[maxn];
//sa[i]名次为i的后缀的起始位置;
//Rank[i]起始位置为i的名次 ->Rank[sa[i]]=i;
//height[i]名次为i和i-1的后缀的最长公共前缀长;
int t1[maxn],t2[maxn],c[maxn];
int Rank[maxn],height[maxn];
bool cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
int s[maxn];
void da(int str[],int n,int m){
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=str[i]]++;//***
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1){
p=0;
for(i=n-j;i<n;i++)y[p++]=i;
for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n)break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++)Rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[Rank[i]-1];
while(s[i+k]==s[j+k])k++;
height[Rank[i]]=k;
}
}

bool isok(int n,int k){
int ma=sa[1],mi=sa[1];
for(int i=2;i<=n;i++){
if(height[i]<k)ma=mi=sa[i];
else{
if(sa[i]<mi)mi=sa[i];
if(sa[i]>ma)ma=sa[i];
if(ma-mi>k)return true;
}
}return false;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
while(cin>>n&&n){
for(int i=0;i<n;i++)cin>>s[i];
for(int i=n-1;i>0;i--)s[i]=s[i]-s[i-1]+90;//因为四十行要用s[i]的值做下表所以不能出现负数
n--;//变化后的长度-1
for(int i=0;i<n;i++)s[i]=s[i+1];
s[n]=0;
da(s,n+1,200);

int ans=-1;
int l=1,r=n/2;
while(l<=r){
int mid=(l+r)/2;
if(isok(n,mid)){
ans=mid;
l=mid+1;
}
else r=mid-1;
}
if(ans<4)cout<<0<<endl;
else cout<<ans+1<<endl;
}
return 0;
}

附加1. #1403 : 后缀数组一·重复旋律

题意

最长可重叠重复K次子串问题。

题解

二分答案

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
#define rank rankk
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int t1[maxn],t2[maxn],c[maxn];
bool cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=str[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1){
p=0;
for(i=n-j;i<n;i++)y[p++]=i;
for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n)break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
int rank[maxn];//后缀i在sa[]中的排名
int height[maxn];//sa[i]与sa[i-1]的LCP(最长公共前缀)
int str[maxn];
int r[maxn];
int sa[maxn];//sa[i]表示排名弟i小的后缀的下标
int n,m;
int solve(int k){
int tmp=1;
for(int i=1;i<n;){
int cnt=1;
int j=i+1;
while(height[j]>=k&&j<=n)j++,cnt++;
tmp=max(cnt,tmp);
i=j;
}
return tmp>=m;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>n>>m;
for(int i=0;i<n;i++)cin>>str[i];
str[n]=0;//多补一个0
da(str,sa,rank,height,n,128);
int l=0,r=n,ans=-1;
while(l<=r){
int mid=(l+r)>>1;
if(solve(mid))ans=mid,l=mid+1;
else r=mid-1;
}
cout<<ans<<endl;
return 0;
}

附加2. #1407 : 后缀数组二·重复旋律2

题意

最长不可重叠重复子串问题

题解

只要判断长度是否大于二分的就行

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
#define rank rankk
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int t1[maxn],t2[maxn],c[maxn];
bool cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=str[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1){
p=0;
for(i=n-j;i<n;i++)y[p++]=i;
for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n)break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
int rank[maxn];//后缀i在sa[]中的排名
int height[maxn];//sa[i]与sa[i-1]的LCP(最长公共前缀)
int str[maxn];
int r[maxn];
int sa[maxn];//sa[i]表示排名弟i小的后缀的下标
int n,m;
int solve(int k){
int minsa,maxsa;
for(int i=1;i<=n;i++)
if(height[i]<k){
minsa=sa[i];
maxsa=sa[i];
}
else{
minsa=min(minsa,sa[i]);
maxsa=max(maxsa,sa[i]);
if(maxsa-minsa>=k)return true;
}
return false;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>n;
for(int i=0;i<n;i++)cin>>str[i];
str[n]=0;//多补一个0
da(str,sa,rank,height,n,1280);
int l=0,r=n,ans=-1;
while(l<=r){
int mid=(l+r)>>1;
if(solve(mid))ans=mid,l=mid+1;
else r=mid-1;
}
cout<<ans<<endl;
return 0;
}

附加3.#1415 : 后缀数组三·重复旋律3

题意

经典的最长公共子串问题//

如果同一段旋律在作品A和作品B中同时出现过,这段旋律就是A和B共同的部分,比如在abab 在 bababab 和 cabacababc 中都出现过。小Hi想知道两部作品的共同旋律最长是多少?

题解

把两个字符串接在一起就变成了求后缀的最长公共前缀的问题,但是由于这个前缀不能跨越两个字符串,所以我们在第一个字符串后面加上一个没有出现过的字符,再接第二个字符串,然后取所有非同一个串后缀的height的最大值…

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
#define rank rankk
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int t1[maxn],t2[maxn],c[maxn];
bool cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(char str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=str[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1){
p=0;
for(i=n-j;i<n;i++)y[p++]=i;
for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n)break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
int rank[maxn];//后缀i在sa[]中的排名
int height[maxn];//sa[i]与sa[i-1]的LCP(最长公共前缀)
char str[maxn];
char str2[maxn];
int r[maxn];
int sa[maxn];//sa[i]表示排名弟i小的后缀的下标
int n,m;
int solve(int n,int len1){
int ans=0;
for(int i=1;i<=n;i++){
if((sa[i-1]<=len1&&sa[i]>len1)||(sa[i-1]>len1&&sa[i]<=len1))
ans=max(ans,height[i]);
}
return ans;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>str;
int len1=strlen(str);
str[len1]='#';
cin>>str2;
int len2=strlen(str2);
for(int i=0;i<len2;i++){
str[len1+1+i]=str2[i];
}
str[len1+1+len2]=0;
n=len1+len2+1;
da(str,sa,rank,height,n,128);
cout<<solve(n,len1)<<endl;
return 0;
}

附加4.#1419 : 后缀数组四·重复旋律4

题意

重复次数最多的连续字串

小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为长度为 N 的数构成的数列。小Hi在练习过很多曲子以后发现很多作品中的旋律有重复的部分。

我们把一段旋律称为(k,l)-重复的,如果它满足由一个长度为l的字符串重复了k次组成。 如旋律abaabaabaaba是(4,3)重复的,因为它由aba重复4次组成。

小Hi想知道一部作品中k最大的(k,l)-重复旋律。

题解

求重复次数最多的连续字串

假如知道了这个连续子串的开始位置
并且知道了它的长度
那么,此时我们就通过lcp(i,i+len)来进行比较即可

但是,如果枚举长度和开始的位置的话
尽管lcp通过st表可以O(1)查询
但是这个枚举的复杂度是O(n2)的
很显然,需要更快

所以,枚举了长度lenlen之后
我们只需要考虑开始位置为lenlen倍数的地方
如果此时有一个重复串的开始位置不在lenlen的倍数上
很显然的
lcp就会多出一截
所以,我们可以倒推出这个位置
所以,这样枚举复杂度为O(nlogn)

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
#define rank rankk
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int t1[maxn],t2[maxn],c[maxn];
bool cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(char str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=str[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1){
p=0;
for(i=n-j;i<n;i++)y[p++]=i;
for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n)break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
int rank[maxn];//后缀i在sa[]中的排名
int height[maxn];//sa[i]与sa[i-1]的LCP(最长公共前缀)
int RMQ[maxn];
int mm[maxn];
int best[20][maxn];
void initRMQ(int n){
mm[0]=-1;
for(int i=1;i<=n;i++)
mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
for(int i=1;i<=n;i++)best[0][i]=i;
for(int i=1;i<=mm[n];i++)
for(int j=1;j+(1<<i)-1<=n;j++){
int a=best[i-1][j];
int b=best[i-1][j+(1<<(i-1))];
if(RMQ[a]<RMQ[b])best[i][j]=a;
else best[i][j]=b;
}
}
int askRMQ(int a,int b){
int t;
t=mm[b-a+1];
b-=(1<<t)-1;
a=best[t][a];b=best[t][b];
return RMQ[a]<RMQ[b]?a:b;
}
int lcp(int a,int b){//求以a,b开始的字串的最长公共前缀
a=rank[a];b=rank[b];
if(a>b)swap(a,b);
return height[askRMQ(a+1,b)];
}
char str[maxn];
int r[maxn];
int sa[maxn];//sa[i]表示排名弟i小的后缀的下标
int n,m;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>str;
n=strlen(str);
str[n]=0;
da(str,sa,rank,height,n,128);
for(int i=1;i<=n;i++)RMQ[i]=height[i];
initRMQ(n);
int ans=1;
for(int i=1;i<=n;i++){
for(int j=0;j+i<n;j+=i){
int len=lcp(j,j+i);
int k=j-(i-len%i);
int sum=len/i+1;
if(k>=0&&lcp(k,k+i)>=i){
sum++;
}
ans=max(ans,sum);
}
}
cout<<ans<<endl;
return 0;
}

B.UVA - 10829 Gap Substrings

题意

题目即求有多少子串满足ABA的形式,并且满足|A|>0,|B|=G。

题解

搞了我三天三夜的题目

首先可以用后缀数组求出任意两个点的后缀的lcp,然后相同长度减去g就是答案

但是这样复杂度是n^2

然后我们可以发现假设A的长度是L,第一个L在K位置,那么另一个L是K+L+G

然后我们发现如果L和另一个K+L+G判断相同了之后,那么L+1,L+2,…2L-1其实都不用再匹配了

所以我们就直接枚举长度L 然后在0,L,2L,3L进行匹配,注意不仅仅要向后面匹配前缀,还要向前面匹配后缀,后者我们可以把字符串倒置,然后求n-1-k,和n-1-(k+l+g)的lcp 这样两个lcp相加,那A的起点就可以在他们这个范围里面 所以答案就是lcp1+lcp2-L

注意lcp长度不能超过L,因为超过的话就会访问到另一个L的区域了

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
struct DA{
bool cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
int t1[maxn],t2[maxn],c[maxn];
int rank[maxn],height[maxn],RMQ[maxn],mm[maxn];
int best[20][maxn];
int r[maxn];
int sa[maxn];
int str[maxn];
void da(int n,int m){
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=str[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1){
p=0;
for(i=n-j;i<n;i++)y[p++]=i;
for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n)break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
void initRMQ(int n){
for(int i=1;i<=n;i++)RMQ[i]=height[i];
mm[0]=-1;
for(int i=1;i<=n;i++)
mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
for(int i=1;i<=n;i++)best[0][i]=i;
for(int i=1;i<=mm[n];i++)
for(int j=1;j+(1<<i)-1<=n;j++){
int a=best[i-1][j];
int b=best[i-1][j+(1<<(i-1))];
if(RMQ[a]<RMQ[b])best[i][j]=a;
else best[i][j]=b;
}
}
int askRMQ(int a,int b){
int t;
t=mm[b-a+1];
b-=(1<<t)-1;
a=best[t][a];b=best[t][b];
return RMQ[a]<RMQ[b]?a:b;
}
int lcp(int a,int b){
a=rank[a];b=rank[b];
if(a>b)swap(a,b);
return height[askRMQ(a+1,b)];
}
void print(int n){
cout<<"sa[] ";
for(int i=0;i<=n;i++)cout<<sa[i]<<" ";cout<<endl;
cout<<"rank[] ";
for(int i=0;i<=n;i++)cout<<rank[i]<<" ";cout<<endl;
cout<<"height[] ";
for(int i=0;i<=n;i++)cout<<height[i]<<" ";cout<<endl;
}
}AA,BB;
char s[maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
/*int len=strlen(s);
for(int i=0;i<len;i++)AA.str[i]=s[i];
AA.da(len,120);
AA.print(len);*/
int t;
cin>>t;
int CC=1;
while(t--){
int g;
cin>>g>>s;
int len=strlen(s);
//cout<<"s=="<<s<<endl;
//cout<<"len="<<len<<endl;
for(int i=0;i<len;i++){
AA.str[i]=s[i]-'a'+1;
BB.str[i]=s[len-i-1]-'a'+1;
}
AA.da(len,30);BB.da(len,30);
AA.initRMQ(len);BB.initRMQ(len);
//AA.print(len);
// BB.print(len);
ll ans=0;
for(int i=1;i<=len;i++){
for(int j=0;j+i+g<len;j+=i){
int l=j,r=j+i+g;
int lll=min(i,AA.lcp(l,r));
int rrr=min(i,BB.lcp(len-l-1,len-r-1));
int len=(lll+rrr);
if(len>=i)ans+=ll(1LL*len-i);
}
}
cout<<"Case "<<CC++<<": ";
cout<<ans<<endl;
}
return 0;
}
/*
2
1 bbaabaaaaa
5 abxxxxxab
*/

dc3

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
struct DA{
#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
int sa[maxn*20],rank[maxn*20],height[maxn*20],str[maxn*20];
int wa[maxn*20],wb[maxn*20],wv[maxn*20],wss[maxn*20];
int c0(int *r,int a,int b){
return r[a]==r[b]&&r[a+1]==r[b+1]&&r[a+2]==r[b+2];
}
int c12(int k,int *r,int a,int b){
if(k==2)
return r[a]<r[b]||(r[a]==r[b]&&c12(1,r,a+1,b+1));
else return r[a]<r[b]||(r[a]==r[b]&&wv[a+1]<wv[b+1]);
}
void sort(int *r,int *a,int *b,int n,int m){
int i;
for(i=0;i<n;i++)wv[i]=r[a[i]];
for(i=0;i<m;i++)wss[i]=0;
for(i=0;i<n;i++)wss[wv[i]]++;
for(i=1;i<m;i++)wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
b[--wss[wv[i]]]=a[i];
}
void dc3(int *r,int *sa,int n,int m){
int i,j,*rn=r+n;
int *san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;
r[n]=r[n+1]=0;
for(i=0;i<n;i++)if(i%3!=0)wa[tbc++]=i;
sort(r+2,wa,wb,tbc,m);
sort(r+1,wb,wa,tbc,m);
sort(r,wa,wb,tbc,m);
for(p=1,rn[F(wb[0])]=0,i=1;i<tbc;i++)
rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;
if(p<tbc)dc3(rn,san,tbc,p);
else for(i=0;i<tbc;i++)san[rn[i]]=i;
for(i=0;i<tbc;i++)if(san[i]<tb)wb[ta++]=san[i]*3;
if(n%3==1)wb[ta++]=n-1;
sort(r,wb,wa,ta,m);
for(i=0;i<tbc;i++)wv[wb[i]=G(san[i])]=i;
for(i=0,j=0,p=0;i<ta&&j<tbc;p++)
sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];
for(;i<ta;p++)sa[p]=wa[i++];
for(;j<tbc;p++)sa[p]=wb[j++];
}
void da(int n,int m){
for(int i=n;i<n*3;i++)str[i]=0;
dc3(str,sa,n+1,m);
int i,j,k=0;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
void print(int n){
cout<<"sa[] ";
for(int i=0;i<=n;i++)cout<<sa[i]<<" ";cout<<endl;
cout<<"rank[] ";
for(int i=0;i<=n;i++)cout<<rank[i]<<" ";cout<<endl;
cout<<"height[] ";
for(int i=0;i<=n;i++)cout<<height[i]<<" ";cout<<endl;
}
}DA;

A.Mediocre String Problem (2018南京M,回文+LCP)

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
struct DA{
#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
int sa[maxn*20],rank[maxn*20],height[maxn*20],str[maxn*20];
int wa[maxn*20],wb[maxn*20],wv[maxn*20],wss[maxn*20];
int c0(int *r,int a,int b){
return r[a]==r[b]&&r[a+1]==r[b+1]&&r[a+2]==r[b+2];
}
int c12(int k,int *r,int a,int b){
if(k==2)
return r[a]<r[b]||(r[a]==r[b]&&c12(1,r,a+1,b+1));
else return r[a]<r[b]||(r[a]==r[b]&&wv[a+1]<wv[b+1]);
}
void sort(int *r,int *a,int *b,int n,int m){
int i;
for(i=0;i<n;i++)wv[i]=r[a[i]];
for(i=0;i<m;i++)wss[i]=0;
for(i=0;i<n;i++)wss[wv[i]]++;
for(i=1;i<m;i++)wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
b[--wss[wv[i]]]=a[i];
}
void dc3(int *r,int *sa,int n,int m){
int i,j,*rn=r+n;
int *san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;
r[n]=r[n+1]=0;
for(i=0;i<n;i++)if(i%3!=0)wa[tbc++]=i;
sort(r+2,wa,wb,tbc,m);
sort(r+1,wb,wa,tbc,m);
sort(r,wa,wb,tbc,m);
for(p=1,rn[F(wb[0])]=0,i=1;i<tbc;i++)
rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;
if(p<tbc)dc3(rn,san,tbc,p);
else for(i=0;i<tbc;i++)san[rn[i]]=i;
for(i=0;i<tbc;i++)if(san[i]<tb)wb[ta++]=san[i]*3;
if(n%3==1)wb[ta++]=n-1;
sort(r,wb,wa,ta,m);
for(i=0;i<tbc;i++)wv[wb[i]=G(san[i])]=i;
for(i=0,j=0,p=0;i<ta&&j<tbc;p++)
sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];
for(;i<ta;p++)sa[p]=wa[i++];
for(;j<tbc;p++)sa[p]=wb[j++];
}
void da(int n,int m){
for(int i=n;i<n*3;i++)str[i]=0;
dc3(str,sa,n+1,m);
int i,j,k=0;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
void print(int n){
cout<<"sa[] ";
for(int i=0;i<=n;i++)cout<<sa[i]<<" ";cout<<endl;
cout<<"rank[] ";
for(int i=0;i<=n;i++)cout<<rank[i]<<" ";cout<<endl;
cout<<"height[] ";
for(int i=0;i<=n;i++)cout<<height[i]<<" ";cout<<endl;
}
}DA;
struct PalTree{
int next[maxn][26],fail[maxn],cnt[maxn],num[maxn],len[maxn],S[maxn],last,n,p;
int newnode(int l){
for(int i=0;i<26;i++)next[p][i]=0;
cnt[p]=num[p]=0;len[p]=l;return p++;
}
void init(){
p=0;newnode(0);newnode(-1);last=0;n=0;S[n]=-1;fail[0]=1;
}
int get_fail(int x){
while(S[n-len[x]-1]!=S[n])x=fail[x];return x;
}
int add(int c){
c-='a';S[++n]=c;int cur=get_fail(last);
if(!next[cur][c]){
int now=newnode(len[cur]+2);
fail[now]=next[get_fail(fail[cur])][c];
next[cur][c]=now;num[now]=num[fail[now]]+1;
}
last=next[cur][c];cnt[last]++;return num[last];
}
void count(){for(int i=p-1;i>=0;i--)cnt[fail[i]]+=cnt[i];}
}PAM;
char s[maxn],t[maxn];
int num[maxn],cnt[maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>s>>t;
int lens=strlen(s),lent=strlen(t);
int len=0;PAM.init();
for(int i=0;i<lens;i++)DA.str[len++]=s[lens-i-1]-'a'+1,cnt[lens-i-1]=PAM.add(s[lens-i-1]);
DA.str[len++]=30;
for(int i=0;i<lent;i++)DA.str[len++]=t[i]-'a'+1;
DA.str[len]=0;
DA.da(len,200);
int p=DA.rank[lens+1];
//DA.print(len);
int now=len+1;
for(int i=p-1;i>=0;i--){
now=min(now,DA.height[i+1]);
if(DA.sa[i]>=0&&DA.sa[i]<lens){
num[lens-1-DA.sa[i]]=now;
}
}
now=len+1;
for(int i=p+1;i<=len;i++){
now=min(now,DA.height[i]);
if(DA.sa[i]>=0&&DA.sa[i]<lens){
num[lens-1-DA.sa[i]]=now;
}
}
ll ans=0;
for(int i=0;i<lens-1;i++){
ans+=1LL*num[i]*cnt[i+1];
}
cout<<ans<<endl;
return 0;
}

I.POJ - 3415 Common Substrings

单调栈优化

题意

求长度不小于K的公共子串的个数。

题解

对于一个LCP 贡献是len(lcp)-k+1

我们可以将height进行分组,大于等于k的在同一组,如果两个后缀的最长公共子串>=k,那么它们肯定在同一个组内。现在从头开始扫,每遇到A的后缀时,就统计一下它和它前面的B的后缀能组成多少长度>=k的公共子串,然后再反过来处理B的后缀即可,一共需要扫两遍。但是这样的时间复杂度是O(n^2),是行不通的。

因为两个后缀的LCP是它们之间的最小height值,所以可以维护一个自底向上递增的单调栈,如果有height值小于了当前栈顶的height值,那么大于它的那些只能按照当前这个小的值来计算。这样分别处理两次,一次处理A的后缀,一次处理B的后缀。需要记录好栈里的和以及每个组内的后缀数。

注意longlong

18.I1

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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#include<string>
#include<iostream>
#include<iosfwd>
#include<cmath>
#include<cstring>
#include<stdlib.h>
#include<stdio.h>
#include<cstring>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=2e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
struct DA{
#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
int sa[maxn*10],rank[maxn*10],height[maxn*10],str[maxn*10];
int wa[maxn*10],wb[maxn*10],wv[maxn*10],wss[maxn*10];
int RMQ[maxn],mm[maxn],best[20][maxn];;
int c0(int *r,int a,int b){
return r[a]==r[b]&&r[a+1]==r[b+1]&&r[a+2]==r[b+2];
}
int c12(int k,int *r,int a,int b){
if(k==2)
return r[a]<r[b]||(r[a]==r[b]&&c12(1,r,a+1,b+1));
else return r[a]<r[b]||(r[a]==r[b]&&wv[a+1]<wv[b+1]);
}
void sort(int *r,int *a,int *b,int n,int m){
int i;
for(i=0;i<n;i++)wv[i]=r[a[i]];
for(i=0;i<m;i++)wss[i]=0;
for(i=0;i<n;i++)wss[wv[i]]++;
for(i=1;i<m;i++)wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
b[--wss[wv[i]]]=a[i];
}
void dc3(int *r,int *sa,int n,int m){
int i,j,*rn=r+n;
int *san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;
r[n]=r[n+1]=0;
for(i=0;i<n;i++)if(i%3!=0)wa[tbc++]=i;
sort(r+2,wa,wb,tbc,m);
sort(r+1,wb,wa,tbc,m);
sort(r,wa,wb,tbc,m);
for(p=1,rn[F(wb[0])]=0,i=1;i<tbc;i++)
rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;
if(p<tbc)dc3(rn,san,tbc,p);
else for(i=0;i<tbc;i++)san[rn[i]]=i;
for(i=0;i<tbc;i++)if(san[i]<tb)wb[ta++]=san[i]*3;
if(n%3==1)wb[ta++]=n-1;
sort(r,wb,wa,ta,m);
for(i=0;i<tbc;i++)wv[wb[i]=G(san[i])]=i;
for(i=0,j=0,p=0;i<ta&&j<tbc;p++)
sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];
for(;i<ta;p++)sa[p]=wa[i++];
for(;j<tbc;p++)sa[p]=wb[j++];
}
void da(int n,int m){
for(int i=n;i<n*3;i++)str[i]=0;
dc3(str,sa,n+1,m);
int i,j,k=0;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}

void initRMQ(int n){
for(int i=1;i<=n;i++)RMQ[i]=height[i];
mm[0]=-1;
for(int i=1;i<=n;i++)
mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
for(int i=1;i<=n;i++)best[0][i]=i;
for(int i=1;i<=mm[n];i++)
for(int j=1;j+(1<<i)-1<=n;j++){
int a=best[i-1][j];
int b=best[i-1][j+(1<<(i-1))];
if(RMQ[a]<RMQ[b])best[i][j]=a;
else best[i][j]=b;
}
}
int askRMQ(int a,int b){
int t;
t=mm[b-a+1];
b-=(1<<t)-1;
a=best[t][a];b=best[t][b];
return RMQ[a]<RMQ[b]?a:b;
}
int lcp(int a,int b){
a=rank[a];b=rank[b];
if(a>b)swap(a,b);
return height[askRMQ(a+1,b)];
}
void print(int n){
cout<<"sa[] ";
for(int i=0;i<=n;i++)cout<<sa[i]<<" ";cout<<endl;
cout<<"rank[] ";
for(int i=0;i<=n;i++)cout<<rank[i]<<" ";cout<<endl;
cout<<"height[] ";
for(int i=0;i<=n;i++)cout<<height[i]<<" ";cout<<endl;
}
}DA;
char str[maxn],str1[maxn];
int s[maxn][2];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int k;
while(cin>>k&&k){
cin>>str>>str1;
int lena=strlen(str);
int lenb=strlen(str1);
int n=0;
for(int i=0;i<lena;i++)DA.str[n++]=str[i];
DA.str[n++]=1;
for(int i=0;i<lenb;i++)DA.str[n++]=str1[i];
DA.da(n,300);
ll tot=0,top=0;
ll sum=0;
for(int i=1;i<=n;i++){
if(DA.height[i]<k)top=0,tot=0;
else{
ll cnt=0;
if(DA.sa[i-1]<lena){
cnt++;tot+=(ll)DA.height[i]-k+1;
}
while(top&&DA.height[i]<=s[top-1][0]){
top--;
tot+=(ll)s[top][1]*(DA.height[i]-s[top][0]);
cnt+=(ll)s[top][1];
}
s[top][0]=DA.height[i];s[top++][1]=cnt;
if(DA.sa[i]>lena)sum+=tot;
}
}
tot=top=0;
for(int i=1;i<=n;i++){
if(DA.height[i]<k)top=0,tot=0;
else{
int cnt=0;
if(DA.sa[i-1]>lena){
cnt++;tot+=(ll)DA.height[i]-k+1;
}
while(top&&DA.height[i]<=s[top-1][0]){
top--;
tot+=(ll)s[top][1]*(DA.height[i]-s[top][0]);
cnt+=(ll)s[top][1];
}
s[top][0]=DA.height[i];s[top++][1]=cnt;
if(DA.sa[i]<lena)sum+=tot;
}
}
cout<<sum<<endl;
}
return 0;
}