4556: [Tjoi2016&Heoi2016]字符串 后缀自动机 详细

来源:互联网 发布:暗黑2 1.13 完美 mac 编辑:程序博客网 时间:2024/06/16 16:09

4556: [Tjoi2016&Heoi2016]字符串

Time Limit: 20 Sec  Memory Limit: 128 MB
Submit: 980  Solved: 384
[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

这是一道后缀自动机的好题。。。。至少我觉得是,因为我很菜


但使用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<cstdio>#include<cstdlib>#include<cstring>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<y?x:y;}int get (int now,int L,int R,int l,int r){if (now==0) return 0;if (l==L&&r==R)return 1;int s1=tr[now].s1,s2=tr[now].s2;int mid=(L+R)>>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;}