对于每一次询问,输出答案。
这是一道后缀自动机的好题。。。。至少我觉得是,因为我很菜
但使用SA的人可以无视了。。
然后我最主要的东西是从这里学的:点这里
在这里我学会了很多东西:
1.有关于SAM parent树的理解与应用
2.有关线段树合并的小姿势
根据题解,我们可以先把字符串反过来,然后问题就转换为:给定两个子串a和b,求的a所有子串和b的最长公共后缀的最大值。
然后我们当然要先二分答案啦
那么如何判断呢?
我们可以现在parent树上进行倍增,就可以得到下图中(已将串翻转)ec这一段在SAM上的点,因为parent树上面的点都拥有共同的后缀嘛!
SAM的常用技巧!!!我才不会告诉你我之前理解不是十分透彻呢!
找到点后就好办啦,我们只需要看一下这个right集合中是否有在a,b这一段区间的就可以了啊
至于如何弄出right集合请看下文
接着自然就想到要知道一个点的right集合,注意,不是大小。因为一个点的right集合是他parent树上所有点的集合的并集(有点绕,请自己理解),所以这里可以用线段树合并可做,当然这里的线段树合并有一点点区别,因为一棵树要和多棵合并,然而之前的我做过的都只是上一棵(当然也有可能是我以前的模板太菜了)。
遇到的问题是见下图
(画的很丑,我也不知道是谁画的,所以不要怪我) 但要是你们在别的博客上看到这幅图,那他肯定是抄的
大意就是y是x和z在parent树上的父亲,然后他的right集合就是x和z合并起来,然而如果你直接搞,会发现到最后x多了一个右儿子,这肯定错了,因为当你访问到x时,你会发现他多了一个儿子的儿子=孙子
然后这怎么办呢?我想了很久,于是膜了一下代码,于是就涨姿势了。大概就是要新建点啊(这个想到了,于是我码了一个十分垃圾的东西,这个就不说了),每次对于y都新建一个,具体看代码
还有一点小细节是这题的线段树不需要维护任何东西,只需要知道有没有建立过某一段区间就好了,因为你只需要知道有没有嘛
然后这题大概就做完了吧。。还有什么不懂的可以看看代码(我的代码比较丑,求right的时候还是递归的,接着变量名可能不是常见的i,j,(⊙o⊙)…代码风格因人而异啦),当然也可以留言
时间复杂度O(n*log^2n)
至于空间的话,大概就是线段树的空间吧,略微有点大
似乎没有SA+主席树的优越,那我为什么要用SAM呢,因为我要加深理解啊!
#include
#include
#include
const int N=100005*2;
int n,m;
char ss[N];
struct qq{int son[26],pre,step;}s[N];int tot,Last;
struct qr{int c,s1,s2;}tr[N*20];int num=0;
int rt[N];
int Pos[N];
void change (int &now,int l,int r,int x)
{
if (now==0) now=++num;
tr[now].c++;
if (l==r) return ;
int mid=(l+r)>>1;
if (x<=mid) change(tr[now].s1,l,mid,x);
else change(tr[now].s2,mid+1,r,x);
}
void ins (int x,int pos)
{
int p=Last,np=++tot;
s[np].step=s[p].step+1;
change(rt[np],1,n,pos);Pos[pos]=np;
while (p!=0&&s[p].son[x]==0) s[p].son[x]=np,p=s[p].pre;
if (p==0) s[np].pre=1;
else
{
int q=s[p].son[x];
if (s[q].step==s[p].step+1) s[np].pre=q;
else
{
int nq=++tot;
s[nq]=s[q];
s[nq].step=s[p].step+1;
s[np].pre=s[q].pre=nq;
while (p!=0&&s[p].son[x]==q) s[p].son[x]=nq,p=s[p].pre;
}
}
Last=np;
}
struct qt{int x,y,last;}e[N];int num1,last[N];
void init (int x,int y)
{
num1++;
e[num1].x=x;e[num1].y=y;
e[num1].last=last[x];
last[x]=num1;
return ;
}
int Merge (int x,int y)//注意啦
{
if (!x||!y) return x+y;
int z=++num;
tr[z].s1=Merge(tr[x].s1,tr[y].s1);
tr[z].s2=Merge(tr[x].s2,tr[y].s2);
return z;
}
void dfs (int x)
{
for (int u=last[x];u!=-1;u=e[u].last)
{
int y=e[u].y;
dfs(y);
if (x==1) continue;
rt[x]=Merge(rt[x],rt[y]);
}
return ;
}
int mymin (int x,int y){return x>1;
if (r<=mid) return get(s1,L,mid,l,r);
else if (l>mid) return get(s2,mid+1,R,l,r);
else return get(s1,L,mid,l,mid)+get(s2,mid+1,R,mid+1,r);
}
int fa[N][20];
void dfs1 (int x)
{
fa[x][1]=s[x].pre;
for (int u=2;;u++)
{
if (fa[fa[x][u-1]][u-1]==0) break;
fa[x][u]=fa[fa[x][u-1]][u-1];
}
for (int u=last[x];u!=-1;u=e[u].last)
dfs1(e[u].y);
}
bool check (int x,int now,int l,int r)//这一个答案行不行
{
for (int u=18;u>=1;u--)
if (s[fa[now][u]].step>=x) now=fa[now][u];
if (get(rt[now],1,n,l,r)>0) return true;
return false;
}
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",ss+1);
tot=Last=1;
for (int u=n;u>=1;u--) ins(ss[u]-'a',n-u+1);
num1=0;memset(last,-1,sizeof(last));
for (int u=2;u<=tot;u++) init(s[u].pre,u);
dfs(1);dfs1(1);
while (m--)
{
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
a=n-a+1;b=n-b+1;c=n-c+1;d=n-d+1;
int l=1,r=mymin(a-b+1,c-d+1);
int ans=0;
while (l<=r)
{
int mid=(l+r)>>1;
if (check(mid,Pos[c],b+mid-1,a)==true) {ans=mid;l=mid+1;}
else r=mid-1;
}
printf("%d\n",ans);
}
return 0;
}