4556: [Tjoi2016&Heoi2016]字符串
Time Limit: 20 Sec
Memory Limit: 128 MB
Submit: 195
Solved: 103
[ Submit][ Status][ Discuss]
Description
佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了
一个长为n的字符串s,和m个问题。佳媛姐姐必须正确回答这m个问题,才能打开箱子拿到礼物,升职加薪,出任CE
O,嫁给高富帅,走上人生巅峰。每个问题均有a,b,c,d四个参数,问你子串s[a..b]的所有子串和s[c..d]的最长公
共前缀的长度的最大值是多少?佳媛姐姐并不擅长做这样的问题,所以她向你求助,你该如何帮助她呢?
Input
输入的第一行有两个正整数n,m,分别表示字符串的长度和询问的个数。接下来一行是一个长为n的字符串。接下来
m行,每行有4个数a,b,c,d,表示询问s[a..b]的所有子串和s[c..d]的最长公共前缀的最大值。1<=n,m<=100,000,
字符串中仅有小写英文字母,a<=b,c<=d,1<=a,b,c,d<=n
Output
Sample Input
5 5
aaaaa
1 1 1 5
1 5 1 1
2 3 2 3
2 4 2 3
2 3 2 4
Sample Output
1
1
2
2
2
方法一:后缀自动机+二分答案+倍增+可持久化线段树
后缀自动机的性质是,所有接收状态都表示一个后缀。但题目要求最长公共前缀,所以要将字符串翻转。(字符串翻转的小技巧要掌握...)
于是问题转化为:给定两个子串a和b,求的a所有子串和b的最长公共后缀的最大值。
然后二分答案,问题转化为判断mid是否合法。
判断方法:每个点用一个权值线段树记录right集合。在parent树上倍增,找到mx值第一个包含mid的地方,然后用该点的线段树判断。
这里又遇到另一个问题,线段树的合并。可以证明线段树的合并复杂度为O(n*logn)。
具体证明:http://wenku.baidu.com/link?url=5HN1zM0kty7nAycmsZAYdRNSNLEb_Keepc-e-BNS3fLWc-0e4ON_bdvMtUD_w01ZIkWfbuKGh2LiXBXkbZ04vPJJTXdd7Dlm2OSO-_8gMgG
最终复杂度为O(n*log^2n)。
#include
#include
#include
#include
#include
#include
#define F(i,j,n) for(int i=j;i<=n;i++)
#define D(i,j,n) for(int i=j;i>=n;i--)
#define ll long long
#define N 200005
#define M 4000005
using namespace std;
int n,m;
int pos[N];
int last=1,cnt=1,a[N][26],fa[N][20],mx[N],c[N],q[N];
int tot,rt[N],ls[M],rs[M];
char s[N];
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void insert(int &k,int l,int r,int x)
{
k=++tot;
if (l==r) return;
int mid=(l+r)>>1;
if (x<=mid) insert(ls[k],l,mid,x);
else insert(rs[k],mid+1,r,x);
}
int merge(int x,int y)//线段树的合并
{
if (!x||!y) return x+y;
int z=++tot;
ls[z]=merge(ls[x],ls[y]);rs[z]=merge(rs[x],rs[y]);
return z;
}
bool find(int k,int l,int r,int L,int R)
{
if (!k) return 0;
if (l==L&&r==R) return 1;
int mid=(l+r)>>1;
if (R<=mid) return find(ls[k],l,mid,L,R);
else if (L>mid) return find(rs[k],mid+1,r,L,R);
else return find(ls[k],l,mid,L,mid)||find(rs[k],mid+1,r,mid+1,R);
}
void add(int x,int id)
{
int p=last,np=++cnt;last=np;
mx[np]=mx[p]+1;pos[id]=np;insert(rt[np],1,n,id);
while (p&&!a[p][x]) a[p][x]=np,p=fa[p][0];
if (!p) fa[np][0]=1;
else
{
int q=a[p][x];
if (mx[q]==mx[p]+1) fa[np][0]=q;
else
{
int nq=++cnt;mx[nq]=mx[p]+1;
memcpy(a[nq],a[q],sizeof(a[q]));
fa[nq][0]=fa[q][0];fa[np][0]=fa[q][0]=nq;
while (a[p][x]==q) a[p][x]=nq,p=fa[p][0];
}
}
}
void calc()
{
F(i,1,cnt) c[mx[i]]++;
F(i,1,cnt) c[i]+=c[i-1];
F(i,1,cnt) q[c[mx[i]]--]=i;
D(i,cnt,1)
{
int x=q[i],f=fa[x][0];
rt[f]=merge(rt[f],rt[x]);
}
F(j,1,18) F(i,1,cnt) fa[i][j]=fa[fa[i][j-1]][j-1];
}
bool check(int mid,int x,int l,int r)
{
D(i,18,0) if (mx[fa[x][i]]>=mid) x=fa[x][i];
return find(rt[x],1,n,l,r);
}
int main()
{
n=read();m=read();
scanf("%s",s+1);reverse(s+1,s+n+1);
F(i,1,n) add(s[i]-'a',i);
calc();
F(i,1,m)
{
int a=n-read()+1,b=n-read()+1,c=n-read()+1,d=n-read()+1;
swap(a,b);swap(c,d);
int l=1,r=min(d-c+1,b-a+1),mid,ans=0;
while (l<=r)
{
mid=(l+r)>>1;
if (check(mid,pos[d],a+mid-1,b)) ans=mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",ans);
}
return 0;
}
方法二:后缀数组
这个方法感觉是暴力,但是跑得飞快...
每次在后缀数组上暴力向前和向后找,当height[i]≤ans时停止搜索,相当于一个小的剪枝。
#include
#include
#include
#include
#include
#include
#define F(i,j,n) for(int i=j;i<=n;i++)
#define D(i,j,n) for(int i=j;i>=n;i--)
#define ll long long
#define N 200005
#define inf 1000000000
using namespace std;
int n,m;
int x[N],y[N],c[N],sa[N],rnk[N],h[N];
char s[N];
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void build_sa()
{
int num=26,t;
F(i,1,n) c[x[i]]++;
F(i,2,num) c[i]+=c[i-1];
D(i,n,1) sa[c[x[i]]--]=i;
for(int k=1;k<=n;k<<=1)
{
t=0;
F(i,n-k+1,n) y[++t]=i;
F(i,1,n) if (sa[i]>k) y[++t]=sa[i]-k;
memset(c,0,sizeof(c));
F(i,1,n) c[x[i]]++;
F(i,2,num) c[i]+=c[i-1];
D(i,n,1) sa[c[x[y[i]]]--]=y[i];
swap(x,y);
t=1;x[sa[1]]=1;
F(i,2,n) x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?t:++t;
if (t>=n) break;
num=t;
}
}
void get_h()
{
int tmp=0,x;
F(i,1,n) rnk[sa[i]]=i;
F(i,1,n)
{
x=sa[rnk[i]-1];
if (tmp) tmp--;
while (s[x+tmp]==s[i+tmp]) tmp++;
h[rnk[i]]=tmp;
}
}
int main()
{
n=read();m=read();
scanf("%s",s+1);
F(i,1,n) x[i]=s[i]-'a'+1;
build_sa();
get_h();
F(i,1,m)
{
int a=read(),b=read(),c=read(),d=read();
int pos=rnk[c],ans=0,mn=inf;
if (a<=c&&c<=b) ans=min(d-c+1,b-c+1);
D(i,pos,2)
{
if (h[i]<=ans) break;
mn=min(mn,h[i]);
if (sa[i-1]>=a&&sa[i-1]<=b) ans=max(ans,min(mn,min(d-c+1,b-sa[i-1]+1)));
}
mn=inf;
F(i,pos+1,n)
{
if (h[i]<=ans) break;
mn=min(mn,h[i]);
if (sa[i]>=a&&sa[i]<=b) ans=max(ans,min(mn,min(d-c+1,b-sa[i]+1)));
}
printf("%d\n",ans);
}
return 0;
}