bzoj 4556 [Tjoi2016&Heoi2016]字符串

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;
}

你可能感兴趣的:(bzoj 4556 [Tjoi2016&Heoi2016]字符串)