题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4556
题目分析:我发现我对线段树合并一无所知QAQ。
先讲一种简单的做法:我们可以将后缀数组建出来,对于每个询问二分一个答案mid。然后从Rank[c]往上下两个方向跳,找到一个区间[L,R],使得这个区间的后缀和c开头的后缀的LCP大于等于mid。那么如果sa[L]~sa[R]中有落在[a,b-mid+1]的数,最终答案就大于等于mid。要判断区间[L,R]中是否有属于某个权值区域的数,写个可持久化的权值线段树,然后差分一下就行。
上面的做法代码很短,但常数偏大。其实还可以把反串的后缀自动机建出来,二分完答案mid后,用树上倍增跳到Right(c)-mid所对应的节点,然后看这个节点的Right集中是否包含[b+mid-1,a]中的数(上文的a,b,c均指在反串中的位置)。这可以用线段树合并来实现(当然也可以用DFS序+可持久化线段树,不过我做这题的主要目的是写一写线段树合并)。
为了在省选的赛场上也能码出这种超级数据结构题,我特地测试了一下自己写这题代码的时间。结果我用75min写好了代码,交上去WA了一次,然后用一组手造的小数据检查了一遍,发现有个n+1写成了n,还有线段树合并的时候忘了新建节点qaq,然后再交了一次就……
就不停地RE!!!我不停地debug还是没有发现任何错。到了第二天中午(也就是今天中午),我迫不得已把网上某位dalao的程序copy下来对拍,还是没有拍出任何错QAQ。于是我只好将代码一段一段注释掉,交上BZOJ,看看是哪里RE了。结果我发现是线段树合并出错了。
假设线段树的叶子节点有N个,那么线段树合并的总空间应该是 2Nlog(N) (至少我写的是这样),而我只开了 Nlog(N) 。这个应该比较好证明:考虑权值线段树上某个代表区间[L,R]的节点,一开始每一个数都要单独开一条链,于是这个位置的节点被新建了R-L+1次。然后区间里的每两个数合并,都要新开一次该位置的节点,所以又要最多开R-L次,于是所有节点加起来就是 2Nlog(N) 的空间。
那为什么对拍不出错呢?因为我的字符串是随机生成的,导致建出来的SAM状态数很少。而线段树的叶子节点个数等于SAM的状态数,所以不会爆。我将空间改成 2Nlog(N) 之后MLE了,于是我稍微调小了点空间,终于过了。然后我就对着这道题花掉了75min写代码+一个中午debug的时间……
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=100100;
const int maxm=7000000;
const int maxl=20;
const int maxc=26;
struct Seg
{
int sum;
Seg *lson,*rson;
} tree[maxm];
int Tcur=-1;
struct Tnode;
struct edge
{
Tnode *obj;
edge *Next;
} e[maxn<<1];
int Ecur=-1;
struct Tnode
{
int val,Right;
edge *head;
Seg *Seg_Root;
Tnode *son[maxc],*Fa[maxl],*parent;
} SAM[maxn<<1];
Tnode *Last[maxn];
Tnode *Root;
int cur=-1;
int a[maxn];
char s[maxn];
int n,m;
int A,b,c,d;
Seg *New_Seg()
{
Tcur++;
tree[Tcur].sum=0;
tree[Tcur].lson=tree[Tcur].rson=tree;
return tree+Tcur;
}
Tnode *New_node(int v,int c)
{
cur++;
SAM[cur].val=v;
SAM[cur].Right=c;
SAM[cur].parent=NULL;
SAM[cur].head=NULL;
SAM[cur].Seg_Root=tree;
for (int i=0; i<maxc; i++) SAM[cur].son[i]=NULL;
for (int i=0; i<maxl; i++) SAM[cur].Fa[i]=NULL;
return SAM+cur;
}
void Add(Tnode *x,Tnode *y)
{
Ecur++;
e[Ecur].obj=y;
e[Ecur].Next=x->head;
x->head=e+Ecur;
}
void Update(Seg *&root,int L,int R,int x)
{
if (root==tree) root=New_Seg();
if (L==R) root->sum=1;
else
{
int mid=(L+R)>>1;
if (x<=mid) Update(root->lson,L,mid,x);
else Update(root->rson,mid+1,R,x);
root->sum=root->lson->sum+root->rson->sum;
}
}
void Merge(Seg *&x,Seg *y)
{
if (y==tree) return;
if (x==tree)
{
x=y;
return;
}
Seg *z=x;
x=New_Seg();
(*x)=(*z); //要记得新开节点!!!
Merge(x->lson,y->lson);
Merge(x->rson,y->rson);
x->sum=x->lson->sum+x->rson->sum;
}
void Dfs(Tnode *node)
{
Update(node->Seg_Root,1,n+1,node->Right);
for (edge *p=node->head; p; p=p->Next)
{
Tnode *to=p->obj;
Dfs(to);
Merge(node->Seg_Root,to->Seg_Root);
//我这里的线段树合并要两倍SAM节点数*log(n)的空间,也就是maxn*maxl<<2(注意被卡空间)!!!
}
}
void Build_SAM()
{
Root=New_node(0,n+1);
Last[0]=Root;
for (int i=1; i<=n; i++)
{
Tnode *P=Last[i-1],*NP=New_node(i,i);
Last[i]=NP;
int to=a[i];
while ( P && !P->son[to] ) P->son[to]=NP,P=P->parent;
if (!P) NP->parent=Root;
else
{
Tnode *Q=P->son[to];
if (P->val+1==Q->val) NP->parent=Q;
else
{
Tnode *NQ=New_node(P->val+1,n+1);
for (int i=0; i<maxc; i++) NQ->son[i]=Q->son[i];
NQ->parent=Q->parent;
NP->parent=Q->parent=NQ;
while ( P && P->son[to]==Q ) P->son[to]=NQ,P=P->parent;
}
}
}
for (int i=0; i<=cur; i++)
{
Tnode *P=SAM+i;
P->Fa[0]=P->parent;
}
for (int j=1; j<maxl; j++)
for (int i=0; i<=cur; i++)
{
Tnode *P=SAM+i;
if (P->Fa[j-1]) P->Fa[j]=P->Fa[j-1]->Fa[j-1];
}
for (int i=1; i<=cur; i++)
{
Tnode *P=SAM+i;
Add(P->parent,P);
}
Dfs(Root);
}
Tnode *Jump(int x,int y)
{
Tnode *P=Last[x];
for (int j=maxl-1; j>=0; j--)
if ( P->Fa[j] && P->Fa[j]->val>=y ) P=P->Fa[j];
return P;
}
bool Query(Seg *root,int L,int R,int x,int y)
{
if ( y<L || R<x || root==tree ) return false;
if ( x<=L && R<=y ) return root->sum;
int mid=(L+R)>>1;
bool fL=Query(root->lson,L,mid,x,y);
bool fR=Query(root->rson,mid+1,R,x,y);
return ( fL || fR );
}
bool Judge(int len)
{
Tnode *node=Jump(c,len);
return Query(node->Seg_Root,1,n+1,b+len-1,A);
}
int Binary()
{
int L=0,R=min(A-b+1,c-d+1)+1;
while (L+1<R)
{
int mid=(L+R)>>1;
if ( Judge(mid) ) L=mid;
else R=mid;
}
return L;
}
int main()
{
freopen("4556.in","r",stdin);
freopen("4556.out","w",stdout);
scanf("%d%d",&n,&m);
scanf("%s",&s);
for (int i=1; i<=n; i++) a[i]=s[i-1]-'a';
for (int i=1; i<=(n>>1); i++) swap(a[i],a[n-i+1]);
New_Seg();
Build_SAM();
for (int i=1; i<=m; i++)
{
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 ans=Binary();
printf("%d\n",ans);
}
return 0;
}