4556: [Tjoi2016&Heoi2016]字符串
Time Limit: 20 Sec Memory Limit: 128 MB
Submit: 952 Solved: 374
[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
HINT
Source
看阿当学长的代码肝还肝了两个多小时 2333334666666
我们发现这道鬼畜的题用LCP不好搞,于是翻转一下,变成了求LCS蛤蛤。
把串翻转以后建出来一只后缀自动机,搞出来pre树。
我们惊奇的发现如果区间[a,b]中某个节点和d的节点在pre树上的LCA的深度为dep,那么dep可以来更新答案。
显然不能枚举[a,b]区间里每一个数,那么我们二分答案。
发现其实[c,d]串如果考虑LCS的话c没有什么卵用,只是用来卡答案上界的。设当前答案为 mid ,那么我们从d所在pre树中的位置向上蹦跶到深度最小且满足 step>=mid 的位置,然后如果这个节点的子树中有[a,b]区间对应在pre树上的节点,那么该答案成立。判断可以通过权值线段树的合并来实现。
总复杂度 O(nlog2n)
我一直在想一个问题,为什么Markdown里面的代码片这么丑呢。
//bzoj 4556 字符串
#include
#define ll long long
#define M(a) memset(a,0,sizeof a)
#define fo(i,j,k) for(i=j;i<=k;i++)
using namespace std;
const int M=4000005;
const int mxn=200005;
char s[mxn];
int ls[M],rs[M];
int m,T,len,tot,p,q,np,nq,size;
int b[mxn],C[mxn],root[mxn],pos[mxn],step[mxn],son[mxn][28],pre[mxn][22];
inline void insert(int &x,int l,int r,int v)
{
x=(++size);
int mid=l+r>>1;
if(l==r) return;
if(v<=mid) insert(ls[x],l,mid,v);
else insert(rs[x],mid+1,r,v);
}
inline int merge(int x,int y)
{
if(!x) return y;
if(!y) return x;
int z=(++size);
ls[z]=merge(ls[x],ls[y]);
rs[z]=merge(rs[x],rs[y]);
return z;
}
inline bool find(int x,int l,int r,int L,int R)
{
if(!x) return 0;
if(l==L && r==R) return 1;
int mid=l+r>>1;
if(R<=mid) return find(ls[x],l,mid,L,R);
else if(L>mid) return find(rs[x],mid+1,r,L,R);
else return find(ls[x],l,mid,L,mid)||find(rs[x],mid+1,r,mid+1,R);
}
inline void sam()
{
int i,j,c;
np=tot=1;
fo(i,1,len)
{
c=s[i]-'a'+1,p=np;
step[np=(++tot)]=step[p]+1;
pos[i]=np,insert(root[np],1,len,i);
while(p && !son[p][c])
son[p][c]=np,p=pre[p][0];
if(!p) {pre[np][0]=1;continue;}
q=son[p][c];
if(step[q]==step[p]+1)
pre[np][0]=q;
else
{
step[nq=(++tot)]=step[p]+1;
memcpy(son[nq],son[q],sizeof son[q]);
pre[nq][0]=pre[q][0];
pre[q][0]=pre[np][0]=nq;
while(p && son[p][c]==q)
son[p][c]=nq,p=pre[p][0];
}
}
fo(i,1,tot) b[step[i]]++;
fo(i,1,tot) b[i]+=b[i-1];
fo(i,1,tot) C[b[step[i]]--]=i;
for(i=tot;i>=1;i--)
{
int x=C[i],fa=pre[x][0];
root[fa]=merge(root[fa],root[x]);
}
fo(j,1,20) fo(i,1,tot) pre[i][j]=pre[pre[i][j-1]][j-1];
}
inline bool check(int mid,int x,int l,int r)
{
for(int i=20;i>=0;i--) if(step[pre[x][i]]>=mid) x=pre[x][i];
return find(root[x],1,len,l,r);
}
int main()
{
int i,j,a,b,c,d;
scanf("%d%d",&len,&m);
scanf("%s",s+1);
reverse(s+1,s+len+1);
sam();
while(m--)
{
scanf("%d%d%d%d",&a,&b,&c,&d);
a=len-a+1,b=len-b+1,c=len-c+1,d=len-d+1;
swap(a,b),swap(c,d);
int l=0,r=min(d-c+1,b-a+1);
while(lint mid=(l+r>>1)+1;
if(check(mid,pos[d],a+mid-1,b)) l=mid;
else r=mid-1;
}
printf("%d\n",l);
}
return 0;
}