【BZOJ4698】[SDOI2008] Sandy的卡片(后缀数组+二分)
大致题意: 给你\(N\)个序列,若定义两个相同子串为一个子串内所有数加上一个数后能变成另一个串,求所有序列中的最长相同子串的长度。
简单的转化
首先,我们对题目进行一个简单的转化。
要求子串内所有数加上一个数后能变成另一个串,实际上就是要求这两个子串中相邻元素之差相等。
因此,我们只要将相邻两元素之差存储下来,就变成求最长公共子串的长度了。
后缀数组
要做这道题,我们需要使用后缀数组。
不过此题不需要求\(LCP\),只要将所有序列拼在一起,中间用一个互不相同的字符隔开,再求出\(Height\)数组,然后二分即可。
如何二分
我们可以二分答案\(len\)。
考虑如何验证是否存在一个长度大于等于\(len\)的公共子串。
其实这个问题等价于:是否存在若干连续的后缀,满足它们的\(LCP\ge len\),且它们在\(n\)个序列中皆有分布。
而这其实在求出\(SA\)数组和\(Height\)数组之后直接\(O(n)\)扫一遍就可以了。
乘上二分\(O(logn)\)的时间复杂度,总复杂度是\(O(nlogn)\)的。
注意最后答案要加\(1\)。
代码
#include<bits/stdc++.h>
#define N 1000
#define Len 1500000
#define Gmax(x,y) (x<(y)&&(x=(y)))
using namespace std;
int n,m,len,s[Len+5],p[Len+5];
class Class_FIO
{
private:
#define Fsize 100000
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,Fsize,stdin),A==B)?EOF:*A++)
#define pc(ch) (void)(putchar(ch))
int Top,FoutSIze;char ch,*A,*B,Fin[Fsize],Fout[Fsize],Stack[Fsize];
public:
Class_FIO() {A=B=Fin;}
inline void read(int &x) {x=0;while(!isdigit(ch=tc()));while(x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));}
inline void write(int x) {if(!x) return pc('0');while(x) Stack[++Top]=x%10+48,x/=10;while(Top) pc(Stack[Top--]);}
}F;
class Class_SuffixArray//后缀数组
{
private:
int n,Top,vis[N+5],Stack[N+5],SA[Len+5],Height[Len+5],rk[Len+5],pos[Len+5],tot[Len+5];
inline void RadixSort(int S)
{
register int i;
for(i=0;i<=S;++i) tot[i]=0;
for(i=1;i<=n;++i) ++tot[rk[i]];
for(i=1;i<=S;++i) tot[i]+=tot[i-1];
for(i=n;i;--i) SA[tot[rk[pos[i]]]--]=pos[i];
}
inline void GetSA(int *s)
{
register int i,k,cnt=0,Size=N<<1;
for(i=1;i<=n;++i) rk[pos[i]=i]=s[i];
for(RadixSort(Size),k=1;cnt<n;k<<=1)
{
for(Size=cnt,cnt=0,i=1;i<=k;++i) pos[++cnt]=n-k+i;
for(i=1;i<=n;++i) SA[i]>k&&(pos[++cnt]=SA[i]-k);
for(RadixSort(Size),i=1;i<=n;++i) pos[i]=rk[i];
for(rk[SA[1]]=cnt=1,i=2;i<=n;++i) rk[SA[i]]=(pos[SA[i-1]]^pos[SA[i]]||pos[SA[i-1]+k]^pos[SA[i]+k])?++cnt:cnt;
}
}
inline void GetHeight(int *s)
{
register int i,j,k=0;
for(i=1;i<=n;++i) rk[SA[i]]=i;
for(i=1;i<=n;++i)
{
if(!(rk[i]^1)) continue;
k&&--k,j=SA[rk[i]-1];
while(i+k<=n&&j+k<=n&&!(s[i+k]^s[j+k])) ++k;
Height[rk[i]]=k;
}
}
public:
inline void Init(int len,int *s) {n=len,GetSA(s),GetHeight(s);}
inline bool Check(int t,int x)//O(n)验证
{
register int i;
while(Top) vis[Stack[Top--]]=0;//清空
for(i=1;i<=n;++i)
{
if(Height[i]<x) while(Top) vis[Stack[Top--]]=0;//如果Height[i]<x,即LCP(i,i-1)<x,则说明不可行,清空
if(!vis[p[SA[i]]]&&(vis[Stack[++Top]=p[SA[i]]]=1)&&!(Top^t)) return true;//如果在n个字符串中皆有分布,就返回true
}
return false;//如果找不到,返回false
}
}S;
int main()
{
register int i,j,x,y,z,cnt=0,l,r,mid;
for(F.read(n),i=1;i<=n;++i,s[++len]=N+cnt) for(F.read(x),F.read(y),++cnt,j=1;j<x;++j) F.read(z),s[++len]=z-y,y=z,p[len]=cnt;//读入并存下相邻元素的差值
for(S.Init(len,s),mid=(l=0)+(r=len)+1>>1;l<r;mid=l+r+1>>1) S.Check(n,mid)?l=mid:r=mid-1;//二分答案
return F.write(l+1),0;
}
待到再迷茫时回头望,所有脚印会发出光芒