「后缀数组」学习笔记

理论知识

详见 OI Wiki

模板

后缀排序

一切有关后缀数组问题的必备板子。

求后缀数组模板题,OI Wiki 有详解 。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e6+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N];
char s[N];
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init()
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
cin>>s+1;
n=strlen(s+1);
init();
for(int i=1;i<=n;i++)
cout<<sa[i]<<' ';
}

可重叠最长重复子串

height 数组模板题,OI Wiki 有详解如何求 height 数组。

即求 maxi=1n{heighti}

注意 height1=0

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],ans;
char s[N];
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init()
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n);
cin>>s+1;
init();
for(int i=1;i<=n;i++)
ans=max(ans,height[i]);
cout<<ans;
}

应用

height 数组基本应用

不同子串个数

所有子串的个数为 n(n+1)2 ,其中重复的有 i=1nheighti ,故答案为 n(n+1)2i=1nheighti

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,q,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],ans;
char s[N];
int sta[N],top,a[N],b[N];
struct aa
{
int l,r,id;
}e[N];
bool cmp(aa a,aa b) {return a.l==b.l?a.r>b.r:a.l<b.l;}
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init()
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n);
cin>>s+1;
init();
for(int i=1;i<=n;i++)
ans+=height[i];
cout<<n*(n+1)/2-ans;
}

Milk Patterns G

求出现最少 k 次的子串的最大长度。

出现至少 k 次说明后缀排序后至少有连续 k 个后缀以这个子串为公共串。

即求出没连续 k1heighti 的最小值,这些最小值中的最大值即为所求。

可以用单调队列 O(n) 解决,当然及时不用也足以 AC

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e6+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,K,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],ans;
int s[N];
deque<int>q;
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init()
{
int m=1000000,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n),read(K);
K--;
for(int i=1;i<=n;i++) read(s[i]);
init();
for(int i=1;i<=n;i++)
{
while(!q.empty()&&height[q.front()]>=height[i]) q.pop_front();
q.push_front(i);
while(!q.empty()&&q.back()<=i-K) q.pop_back();
if(i>=K) ans=max(ans,height[q.back()]);
}
write(ans);
}

结合 ST

我们知道 LCP(sal,sar)=mini=l+1r{heighti} ,所以通过 ST 表维护 RMQ 是很常见的。

很多时候 ST 表起的只是一个辅助作用,所以好多结合 ST 表的题还会结合别的。

Yet Another LCP Problem

对于每一组给定的 a,b 数组,不妨将其拼成 c 数组。

我们定义 ansx=i=1lenxj=1lenxLCP(sxin,sxjn) ,那么有:

i=1i=kj=1j=lLCP(sain,sbjn)=anscansaansb2

至于为什么 12 如下:

image

我们知道 i=1lenxj=i+1lenxLCP(sxin,sxjn)=i=1lenxj=1lenxLCP(sxin,sxjn)2 ,不妨直接另 ansx=i=1lenxj=i+1lenxLCP(sxin,sxjn) ,那么求 anscansaansb 即可。

结合单调栈求 ansx

先将 x 数组按照 rk[x1]<rk[x2] 排序,使其符合单调性。

定义 vali=LCP(xi1,xi) ,类似 luogu P4248 差异 这道题,去计算 vali 的贡献,实际上 val 的定义和 height 是类似的,因为 xi 并不连续,所以需要这样操作,显然有 val1=0

接下来单调栈维护的过程与 luogu P4248 差异 是类似的,处理出最靠右的 0l<i,vall<vali 的位置,以及最靠左的 i<rn,valr<vali 的位置,那么 lr 这一段的 LCP 均为 vali ,依据乘法原理, vali 的贡献就是 vali×(il)×(ri) ,不妨定义 li=il,ri=ri

那么 ansx=i=1lenxvali×li×ri

至于 LCP ,可以用 ST 表维护。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=4e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m,ans,sum,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],l[N],r[N],a[N],b[N],c[N],mi[N][30],val[N];
char s[N],t[N];
int sta[N],top;
void clean()
{
memset(rk,0,sizeof(rk));
memset(sa,0,sizeof(sa));
memset(id,0,sizeof(id));
memset(oldrk,0,sizeof(rk));
memset(cnt,0,sizeof(cnt));
memset(key,0,sizeof(key));
memset(height,0,sizeof(height));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
}
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init(char s[],int n)
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
void RMQ()
{
memset(mi,0x3f,sizeof(mi));
for(int i=1;i<=n;i++)
mi[i][0]=height[i];
for(int j=1;j<=log2(n);j++)
for(int i=1;i<=n-(1<<j)+1;i++)
mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
}
int ask(int l,int r)
{
int t=log2(r-l+1);
return min(mi[l][t],mi[r-(1<<t)+1][t]);
}
bool cmp(int a,int b) {return rk[a]<rk[b];}
int calc(int x[],int len)
{
sort(x+1,x+1+len,cmp);
for(int i=2;i<=len;i++)
if(x[i]==x[i-1])
val[i]=n-x[i]+1;
else
val[i]=ask(rk[x[i-1]]+1,rk[x[i]]);
sum=ans=top=0;
for(int i=1;i<=len;i++)
{
while(val[sta[top]]>val[i]) top--;
l[i]=i-sta[top];
sta[++top]=i;
}
val[len+1]=-1,sta[++top]=len+1;
for(int i=len;i>=1;i--)
{
while(val[sta[top]]>=val[i]) top--;
r[i]=sta[top]-i;
sta[++top]=i;
}
for(int i=1;i<=len;i++)
ans+=l[i]*r[i]*val[i];
return ans;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n),read(m);
cin>>s+1;
init(s,n);
RMQ();
for(int i=1,len1,len2;i<=m;i++)
{
read(len1),read(len2);
for(int j=1;j<=len1;j++)
read(a[j]),
c[j]=a[j];
for(int j=1;j<=len2;j++)
read(b[j]),
c[len1+j]=b[j];
cout<<calc(c,len1+len2)-calc(a,len1)-calc(b,len2)<<endl;
}
}

相似子串

我们发现所有子串的左端点都在后缀数组中出现过,问题是考虑右端点。

发现例如后缀 abcd ,可以将其分为 aababcabcd 四个子串,若其与上一个后缀的 LCPab ,则新的子串就剩下 abcabcd

由此可得每个后缀对子串个数的贡献为 leniheighti ,如此得来所有的子串已经是排好序的。

不妨使用前缀和维护,利用二分查找出第 i 个子串处于哪一个后缀中,定义为 posi

对于每次查询 x,y ,处理出 posx,posy ,那么 lenx=heightposx+xsumposx1leny 同理,那么很容易求出子串 x,y 的公共前缀 =min(min(lenx,leny),LCP(saposx,saposy)) ,当然若 posx=posy ,其只能 =min(lenx,leny) ,可以用 ST 表维护 LCP

问题来到如何处理公共后缀,不难想到建一个反串类似的维护,而问题是子串 x,y 在反串中的 pos 是多少。

定义 posi 表示子串 i 在反串中处于哪一个后缀中,有 posx=rkn(saposx+lenx1)+1posy 同理,即该子串右端点的 rk ,再类似上面维护即可。

虽然这样求 pos 不一定绝对准确,但是他能够求出这样的 pos 说明他在 pos 中一定是合法的,所以这样去求也是没有任何问题的,样例 ``ababa 就是个很好的例子,aba``` 的 pos 应该为 2 ,但求出来的 pos=3 ,然而对答案没有影响,可以手动模拟理解一下。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m,ans,sa[N][2],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N][2],sum[N][2],ls[N],mi[2][N][30];
char s[N],t[N];
void clean()
{
memset(rk,0,sizeof(rk));
// memset(sa,0,sizeof(sa));
memset(id,0,sizeof(id));
memset(oldrk,0,sizeof(rk));
memset(cnt,0,sizeof(cnt));
memset(key,0,sizeof(key));
// memset(height,0,sizeof(height));
memset(ls,0,sizeof(ls));
}
void count_sort(int n,int m,bool d)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]][d]=id[i],
cnt[key[i]]--;
}
void init(char s[],int n,bool d)
{
clean();
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m,d);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i][d]>w)
num++,
id[num]=sa[i][d]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m,d);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i][d]]!=oldrk[sa[i-1][d]]||oldrk[sa[i][d]+w]!=oldrk[sa[i-1][d]+w]),
rk[sa[i][d]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1][d]+k]) k++;
height[rk[i]][d]=k;
}
for(int i=1;i<=n;i++)
ls[i]=n-sa[i][d]+1-height[i][d],
sum[i][d]=sum[i-1][d]+ls[i];
}
void RMQ(bool d)
{
memset(mi[d],0x3f,sizeof(mi[d]));
for(int i=1;i<=n;i++)
mi[d][i][0]=height[i][d];
for(int j=1;j<=log2(n);j++)
for(int i=1;i<=n-(1<<j)+1;i++)
mi[d][i][j]=min(mi[d][i][j-1],mi[d][i+(1<<(j-1))][j-1]);
}
int LCP(int l,int r,bool d)
{
l++;
int t=log2(r-l+1);
return min(mi[d][l][t],mi[d][r-(1<<t)+1][t]);
}
int ask(int x,int y,bool d)
{
int l=1,r=n,mid,pos_x,pos_y;
while(l<=r)
{
mid=(l+r)>>1;
if(sum[mid][0]<x) l=mid+1;
if(sum[mid][0]>x) r=mid-1,pos_x=mid;
if(sum[mid][0]==x)
{
pos_x=mid;
break;
}
}
l=1,r=n;
while(l<=r)
{
mid=(l+r)>>1;
if(sum[mid][0]<y) l=mid+1;
if(sum[mid][0]>y) r=mid-1,pos_y=mid;
if(sum[mid][0]==y)
{
pos_y=mid;
break;
}
}
int len_x=height[pos_x][0]+x-sum[pos_x-1][0],len_y=height[pos_y][0]+y-sum[pos_y-1][0];
if(d==1)
pos_x=rk[n-(sa[pos_x][0]+len_x-1)+1],
pos_y=rk[n-(sa[pos_y][0]+len_y-1)+1];
if(pos_x>pos_y) swap(pos_x,pos_y);
return pos_x==pos_y?min(len_x,len_y):min(min(len_x,len_y),LCP(pos_x,pos_y,d));
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n),read(m);
cin>>s+1;
for(int i=1;i<=n;i++)
t[n-i+1]=s[i];
init(s,n,0),init(t,n,1);
RMQ(0),RMQ(1);
for(int i=1,x,y;i<=m;i++)
{
read(x),read(y);
if(x>sum[n][0]||y>sum[n][0]) puts("-1");
else cout<<ask(x,y,0)*ask(x,y,0)+ask(x,y,1)*ask(x,y,1)<<endl;
}
}

优秀的拆分

枚举连续串的长度 |s| ,按照 |s| 对整个串进行分块,对相邻两个块进行 LCP,LCS 查询。

正反存两个串,处理出 SA

定义 prei 表示以 i 结尾的 LCP 长度,sufi 表示以 i 开头的 LCS 长度。

那么答案就等于 i=1n1prei+1×sufi

枚举 len 作为分块的长度,于是有 l=i,r=i+len 两个关键点,若该两个关键点的 LCP+LCSlen ,则更新答案。

因为是区间修改,考虑使用差分维护。

使用结构体封装 SA 会比较方便。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=3e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int T,n,pre[N],suf[N],ans;
struct aa
{
char s[N];
int rk[N],sa[N],id[N],key[N],oldrk[N],cnt[N],height[N],mi[N][30];
void clean()
{
memset(rk,0,sizeof(rk));
memset(sa,0,sizeof(sa));
memset(id,0,sizeof(id));
memset(oldrk,0,sizeof(rk));
memset(cnt,0,sizeof(cnt));
memset(key,0,sizeof(key));
memset(height,0,sizeof(height));
}
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init(char s[],int n)
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
void ST(int n)
{
memset(mi,0x3f,sizeof(mi));
for(int i=1;i<=n;i++)
mi[i][0]=height[i];
for(int j=1;j<=log2(n);j++)
for(int i=1;i<=n-(1<<j)+1;i++)
mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
}
int lcp(int l,int r)
{
l=rk[l],r=rk[r];
if(l>r) swap(l,r);
l++;
int t=log2(r-l+1);
return min(mi[l][t],mi[r-(1<<t)+1][t]);
}
}a,b;
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(T);
while(T--)
{
cin>>a.s+1;
n=strlen(a.s+1);
for(int i=1;i<=n;i++)
b.s[i]=a.s[n-i+1];
a.clean(),b.clean();
a.init(a.s,n),b.init(b.s,n);
a.ST(n),b.ST(n);
for(int i=1;i<=n;i++)
pre[i]=suf[i]=0;
for(int len=1;len<=n/2;len++)
for(int i=1;i<=n;i+=len)
{
int l1=i,r1=i+len,l2=n-(r1-1)+1,r2=n-(l1-1)+1;
int lcp=min(a.lcp(l1,r1),len),lcs=min(b.lcp(l2,r2),len-1);
if(lcp+lcs>=len)
pre[l1-lcs]++,
pre[l1+lcp-len+1]--,
suf[r1-lcs+len-1]++,
suf[r1+lcp]--;
}
for(int i=1;i<=n;i++)
pre[i]+=pre[i-1],suf[i]+=suf[i-1];
ans=0;
for(int i=1;i<n;i++)
ans+=pre[i+1]*suf[i];
write(ans),puts("");
}
}

结合单调栈

因为 LCP(sal,sar)=mini=l+1r{heighti} ,所以很多时候为了优化复杂度去计算 heighti 的贡献时需要结合单调栈。

通常思路有两种,本人主要采取类似于 广告印刷 这道题,处理出每个 heighti 能管辖到的 l,r ,即区间 [lr] 中的最小值为 heighti ,那么通常此时 heighti 能做的贡献就是 (il+1)×(ri+1)×heighti ,依据乘法原理。

差异

结合单调栈板子题。

对于 ii<jnlen(Ti)+len(Tj)2×LCP(Ti,Tj) 这个式子,将其分为两部分计算。

不难计算出 1i<jnlen(Ti)+len(Tj)=n×(n1)×(n+1)2 。首先有 n 个字符串,那么会产生 n×(n1)2 对组合,我们知道每个后缀的长度平均值 =i=1nlenTin=n+12 ,那么两个后缀长度加一起就是 n+1 ,故答案为 n×(n1)×(n+1)2

问题主要是如何求 1i<jnLCP(Ti,Tj)

i=1nj=i+1nLCP(si|s|,j|s|)=i=1nj=i+1nLCP(ssai|s|,saj|s|)=i=1nj=i+1nmink=i+1j{heightk}=i=2nj=inmink=ij{heightk}=i=1nj=inmink=ij{heightk}i=1nminj=1i{heightj}=i=1nj=inmink=ij{heightk}i=1n0=i=1nj=inmink=ij{heightk}

根据 LCP(sal,sar)=mini=l+1r{heighti} ,所以很多时候为了优化复杂度去计算 heighti ,考虑计算每个 heighti 的贡献,于是用到单调栈。

通过单调栈,处理出 max0l<i&heightl<heighti{l} 以及 mini<rn+1&heightr<heighti{r} ,那么 heighti 的贡献就是 (il+1)×(ri+1)×heighti ,不妨设 li=il+1,ri=ri+1 ,那么其贡献就是 li×ri×heighti

那么最后答案就是 n×(n1)×(n+1)22×i=1nli×ri×heighti

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=5e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],ans,l[N],r[N];
char s[N];
int sta[N],top;
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init()
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
cin>>s+1;
n=strlen(s+1);
init();
for(int i=1;i<=n;i++)
{
while(height[sta[top]]>height[i]) top--;
l[i]=i-sta[top];
sta[++top]=i;
}
sta[++top]=n+1,height[n+1]=-1;
for(int i=n;i>=1;i--)
{
while(height[sta[top]]>=height[i]) top--;
r[i]=sta[top]-i;
ans-=2ll*l[i]*r[i]*height[i];
sta[++top]=i;
}
ans+=1ll*n*(n-1)*(n+1)/2;
cout<<ans;
}

找相同字符

设两个串为 t1,t2 ,那么将两个串拼起来成为 s ,中间用分隔符 # 隔开,定义 ansx 表示与差异所求一样的东西,那么答案就是 anssanst1anst2

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=4e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m2,m1,ans,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],l[N],r[N];
char s[N],t2[N],t1[N];
int sta[N],top;
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init(char s[],int n)
{
memset(rk,0,sizeof(rk));
memset(sa,0,sizeof(sa));
memset(id,0,sizeof(id));
memset(oldrk,0,sizeof(rk));
memset(cnt,0,sizeof(cnt));
memset(key,0,sizeof(key));
memset(height,0,sizeof(height));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
int calc(char s[],int len)
{
init(s,len);
ans=top=0;
for(int i=1;i<=len;i++)
{
while(height[sta[top]]>height[i]) top--;
l[i]=i-sta[top];
sta[++top]=i;
}
int x=height[len+1];
sta[++top]=len+1,height[len+1]=-1;
for(int i=len;i>=1;i--)
{
while(height[sta[top]]>=height[i]) top--;
r[i]=sta[top]-i;
ans+=l[i]*r[i]*height[i];
sta[++top]=i;
}
height[len+1]=x;
return ans;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
cin>>t1+1>>t2+1;
m1=strlen(t1+1),m2=strlen(t2+1);
for(int i=1;i<=m1;i++)
s[i]=t1[i];
s[m1+1]='#';
for(int i=1;i<=m2;i++)
s[m1+1+i]=t2[i];
n=m1+m2+1;
cout<<calc(s,n)-calc(t2,m2)-calc(t1,m1);
}

Yet Another LCP Problem

上面已有。

多个串拼合

大多数找不同字符串的公共串的问题都需要将多个字符串拼起来处理。

字符加密

s 复制一遍变成 ss ,就转化为后缀排序问题。

sain2 ,就输出 ssai+n21

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N];
char s[N];
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init()
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
cin>>s+1;
n=strlen(s+1);
for(int i=1;i<=n;i++)
s[i+n]=s[i];
n*=2;
init();
for(int i=1;i<=n;i++)
if(sa[i]<=n/2)
cout<<s[sa[i]+n/2-1];
}

公共串

t1tn 拼起来得到 s=t1t1t2t2tntn ,其中 ti 表示分隔符,每个分隔符不相同。

那么对于每个区间 [l,r] 中对于任意一个 ti 均有一个后缀的排名在该区间中,则答案为 max1l<r|s|{mini=l+1r{heighti}}

使用双指针加单调队列维护即可。

接下来详细解释怎么统计是否每个 ti 均有后缀的排名存在于区间 |l,r| 里。

定义两个函数 add,del ,分别处理加入一个元素与去除一个元素的情况。

先处理出每个 ti 对应的 li,ri ,即左右断点,在此之内的每个元素使 crkj=i,lijri

定义 visi 记录 i 出现的次数,当插入一个指针 i 时,先判断是否为分隔符,若不是则 visci++sum+=[visci=1] ,同理 del 函数对应 viscisum=[visci=0]

那么当 sum=n 时,即 l,r 满足条件。

同时因为 LCP(sal,sar)=mini=l+1r{heighti} ,所以我们希望这个区间尽可能小,那么枚举 r ,同时当 l,r 满足条件的前提下另 l 尽可能大,从而使处理答案的复杂度控制在 O(n)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m,ans,sum,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],l[N],r[N],c[N],v[N],L[N],R[N];
char s[N],t[N];
deque<int>q;
void clean()
{
memset(rk,0,sizeof(rk));
memset(sa,0,sizeof(sa));
memset(id,0,sizeof(id));
memset(oldrk,0,sizeof(rk));
memset(cnt,0,sizeof(cnt));
memset(key,0,sizeof(key));
memset(height,0,sizeof(height));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
}
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init(char s[],int n)
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
void add(int x,int &sum)
{
if(c[x]==0) return ;
v[c[x]]++;
sum+=(v[c[x]]==1);
}
void del(int x,int &sum)
{
if(c[x]==0) return ;
v[c[x]]--;
sum-=(v[c[x]]==0);
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(m);
for(int i=1;i<=m;i++)
{
cin>>t+1;
int len=strlen(t+1);
L[i]=n+1;
for(int j=1;j<=len;j++)
s[++n]=t[j];
R[i]=n;
s[++n]=i+'#';
}
init(s,n);
for(int i=1;i<=m;i++)
for(int j=L[i];j<=R[i];j++)
c[rk[j]]=i;
add(1,sum);
for(int l=1,r=2;r<=n;r++)
{
while(!q.empty()&&height[q.front()]>=height[r])
q.pop_front();
q.push_front(r);
add(r,sum);
if(sum>=m)
{
while(sum>=m&&l<=r-1)
del(l,sum),
l++;
l--;
add(l,sum);
}
while(!q.empty()&&q.back()<=l)
q.pop_back();
if(sum==m)
ans=max(ans,height[q.back()]);
}
cout<<ans;
}

Sandy 的卡片

查分后变为 公共串 ,因为差分后长度会 1 ,所以最后答案要 +1

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m,ans,sum,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],l[N],r[N],c[N],v[N],L[N],R[N];
int s[N],t[N];
deque<int>q;
void clean()
{
memset(rk,0,sizeof(rk));
memset(sa,0,sizeof(sa));
memset(id,0,sizeof(id));
memset(oldrk,0,sizeof(rk));
memset(cnt,0,sizeof(cnt));
memset(key,0,sizeof(key));
memset(height,0,sizeof(height));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
}
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init(int s[],int n)
{
int m=5000,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
void add(int x,int &sum)
{
if(c[x]==0) return ;
v[c[x]]++;
sum+=(v[c[x]]==1);
}
void del(int x,int &sum)
{
if(c[x]==0) return ;
v[c[x]]--;
sum-=(v[c[x]]==0);
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(m);
for(int i=1,len;i<=m;i++)
{
cin>>len;
L[i]=n+1;
for(int j=1;j<=len;j++)
{
read(t[j]);
if(j>=2)
s[++n]=2000+t[j]-t[j-1];
}
R[i]=n;
s[++n]=i+2000;
}
init(s,n);
for(int i=1;i<=m;i++)
for(int j=L[i];j<=R[i];j++)
c[rk[j]]=i;
add(1,sum);
for(int l=1,r=2;r<=n;r++)
{
while(!q.empty()&&height[q.front()]>=height[r])
q.pop_front();
q.push_front(r);
add(r,sum);
if(sum>=m)
{
while(sum>=m&&l<=r-1)
del(l,sum),
l++;
l--;
add(l,sum);
}
while(!q.empty()&&q.back()<=l)
q.pop_back();
if(sum==m)
ans=max(ans,height[q.back()]);
}
cout<<ans+1;
}

找相同字符

上面已有,由于涉及将两个串拼起来的思路,所以也放在了这里。

Yet Another LCP Problem

上面已有,同样的涉及将两个串拼起来。

结合莫队

喵星球上的点名

将所有姓名拼起来,中间用不同分隔符断开,处理出 SA

对于每一个查询串,发现其对应原串可以匹配位置可以用区间 [l,r] 表示,可以用每个位置都二分处理出左右端点。

对于每个后缀处理出其对应那个人,用 ai 表示。

于是问题一转变成区间数颜色问题,最基本的莫队维护即可,可见 HH 的项链

至于第二问,不妨在添加和删除操作时传入参数 i ,即处理到哪个问题。

当新添加入该串时,另其 ans+=toti+1 ,删除该串时,则另其 anstoti+1tot 表示合法的查询个数,由此问题二解决。

点击查看代码
#include<bits/stdc++.h>
// #define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=5e6+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,m,t,tot,tot_m,s[N],id[N],rk[N],sa[N],cnt[N],oldrk[N],key[N],height[N],a[N],pos[N],anss[N],ans,ans_tot[N];
struct aa
{
int l,r,id;
}e[N];
bool cmp(aa a,aa b) {return pos[a.l]==pos[b.l]?a.r<b.r:a.l<b.l;}
void clean()
{
memset(rk,0,sizeof(rk));
memset(sa,0,sizeof(sa));
memset(id,0,sizeof(id));
memset(oldrk,0,sizeof(rk));
memset(cnt,0,sizeof(cnt));
memset(key,0,sizeof(key));
memset(height,0,sizeof(height));
}
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init(int s[],int n)
{
int m=300000,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
void add(int &ans,int x,int i)
{
cnt[a[x]]++;
if(cnt[a[x]]==1)
ans++,
ans_tot[a[x]]+=tot_m-i+1;
}
void del(int &ans,int x,int i)
{
cnt[a[x]]--;
if(cnt[a[x]]==0)
ans--,
ans_tot[a[x]]-=tot_m-i+1;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n),read(m);
t=sqrt(n);
int tmp=10000;
for(int i=1,len;i<=n;i++)
{
read(len);
for(int j=1;j<=len;j++)
read(s[++tot]),
a[tot]=i;
s[++tot]=++tmp;
read(len);
for(int j=1;j<=len;j++)
read(s[++tot]),
a[tot]=i;
s[++tot]=++tmp;
pos[i]=(i-1)/t+1;
}
init(s,tot);
for(int i=1,len;i<=m;i++)
{
read(len);
int ll=1,rr=tot;
for(int j=1,x;j<=len;j++)
{
read(x);
int l=ll,r=rr,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(s[sa[mid]+j-1]<x) l=mid+1;
else r=mid-1;
}
int ls=l;
l=ll,r=rr;
while(l<=r)
{
int mid=(l+r)>>1;
if(s[sa[mid]+j-1]<=x) l=mid+1;
else r=mid-1;
}
ll=ls,rr=r;
}
if(ll<=rr) e[++tot_m]={ll,rr,i};
}
sort(e+1,e+1+tot_m,cmp);
int l=1,r=0;
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=tot_m;i++)
{
while(l>e[i].l) add(ans,sa[--l],i);
while(l<e[i].l) del(ans,sa[l++],i);
while(r<e[i].r) add(ans,sa[++r],i);
while(r>e[i].r) del(ans,sa[r--],i);
anss[e[i].id]=ans;
}
for(int i=1;i<=m;i++)
write(anss[i]),puts("");
for(int i=1;i<=n;i++)
write(ans_tot[i]),cout<<' ';
}

结合单调队列

因为 LCP(sal,sar)=mini=l+1r{heighti} ,所以类似于求带限制的最长公共子串问题时常结合单调队列,常见的形式为求 max1l<r|s|{mini=l+1r{heighti}} ,且同时常存在队内长度的限制。

下面的例题前面都已经涉及,就只沾题目了。

Milk Patterns G

公共串

Sandy 的卡片

结合并查集

品酒大会

即求后缀树组划分成连续若干个 height 长度 r 的段并统计每一段的答案。

我们发现若其为 “r 相似” 的,他还会对 “0r 相似” 做出贡献,当给定的 rr1 时,新的区间为 区间 r1 与区间 r 合并所得,故此维护一个并查集,每次合并两个相邻的区间,并维护统计信息即可。

不妨将 r 按照 height 递减排序,那么每次合并的就是 sarisari1 ,即统计 heightri 的贡献。

定义 sumr 表示 r 相似的方案数,ansr 表示其美味度的最大值。

对于两个区间 x,y 合并,维护 maxx,minx,sizexy 同理,分别表示其 ai 的最大值、最小值,以及该区间元素的个数。

不妨定义 z 表示 heightri ,那么有:

  • ansz=max{ansz,maxx×maxy,minx×miny} ,防止负负得正的情况。

  • sumz+=sizex×sizey ,可以理解一下。

  • 之后就是基本的合并操作:

    • maxx=max(maxx,maxy)

    • minx=min(minx,mixy)

    • sizex+=sizey

    • fy=x

    当然最开始的 x=find(x),y=find(y) 是必须的。

最后统计答案即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=3e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m,ans[N],sum[N],sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],a[N],f[N],r[N],mi[N],mx[N],siz[N],maxx[N];
char s[N];
void clean()
{
memset(rk,0,sizeof(rk));
memset(sa,0,sizeof(sa));
memset(id,0,sizeof(id));
memset(oldrk,0,sizeof(rk));
memset(cnt,0,sizeof(cnt));
memset(key,0,sizeof(key));
memset(height,0,sizeof(height));
}
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init(char s[],int n)
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
int find(int x)
{
return f[x]==x?x:f[x]=find(f[x]);
}
bool cmp(int a,int b) {return height[a]>height[b];}
void merge(int x,int y,int z)
{
x=find(x),y=find(y);
if(x==y) return ;
maxx[x]=max({maxx[x],maxx[y],mx[x]*mx[y],mi[x]*mi[y]});
sum[z]+=siz[x]*siz[y];
ans[z]=max(ans[z],maxx[x]);
mx[x]=max(mx[x],mx[y]);
mi[x]=min(mi[x],mi[y]);
f[y]=x;
siz[x]+=siz[y];
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n);
cin>>s+1;
init(s,n);
memset(maxx,-0x3f,sizeof(maxx));
memset(ans,-0x3f,sizeof(ans));
for(int i=1;i<=n;i++)
read(a[i]),
r[i]=f[i]=i,
siz[i]=1,
mx[i]=mi[i]=a[i];
sort(r+1,r+1+n,cmp);
for(int i=1;i<=n;i++)
merge(sa[r[i]],sa[r[i]-1],height[r[i]]);
for(int i=n-1;i>=0;i--)
sum[i]+=sum[i+1],
ans[i]=max(ans[i],ans[i+1]);
for(int i=0;i<=n-1;i++)
cout<<sum[i]<<' '<<(sum[i]!=0)*ans[i]<<endl;
}
posted @   卡布叻_周深  阅读(30)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示