字符串(tjoi2016,heoi2016,bzoj4556)(sam(后缀自动机)+线段树合并+倍增+二分答案)

佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了
一个长为\(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

题意:

中文题面,不解释

题解:

首先前缀不好处理,我们把字符串翻转。

然后发现很难直接求出答案,就采取二分最长后缀并验证。我们只需要验证左区间的后缀是否出现在右区间就行了。

然后用倍增在\(parent\)树上找到对应该子串的节点,并求出该节点能覆盖的节点(他子树中的节点),所以我们可以用线段树合并来求解。

#include<bits/stdc++.h>
#define mid (l+(r-l)/2)
using namespace std;
const int N=200010;
int lson[N*30],rson[N*30],sum[N*30];
int n,m,head[N],nxt[N],bian[N],p,q,np,nq,mp[N],pos[N],cnt,last;
int root[N],ch[N][27],fa[N],l[N],tot,sz,f[N][19],dep[N];
char s[N];
void ins(int x)
{
    int c=s[x]-'a'+1;
    p=last; np=++cnt; last=np; mp[np]=x; pos[x]=np;
    l[np]=l[p]+1;
    for (;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
    if (!p) fa[np]=1;
    else {
        q=ch[p][c];
        if (l[p]+1==l[q]) fa[np]=q;
        else {
            nq=++cnt; l[nq]=l[p]+1;
            memcpy(ch[nq],ch[q],sizeof(ch[q]));
            fa[nq]=fa[q];
            fa[q]=fa[np]=nq;
            for (;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
        }
    }
}
void add(int x,int y){
    tot++,bian[tot]=y,nxt[tot]=head[x],head[x]=tot;
}
int updata(int pre,int l,int r,int x){
    int rt=++sz; sum[rt]=sum[pre]+1;
    if (l==r) return rt;
    lson[rt]=lson[pre];
    rson[rt]=rson[pre];
    if (x<=mid) lson[rt]=updata(lson[pre],l,mid,x);
    else rson[rt]=updata(rson[pre],mid+1,r,x);
    return rt;
}
void pushup(int x){
    int l=lson[x],r=rson[x];
    sum[x]=sum[l]+sum[r];
}
int merge(int x,int y){
    if(!x||!y)return x+y;
    int rt=++sz;
    lson[rt]=merge(lson[x],lson[y]);
    rson[rt]=merge(rson[x],rson[y]);
    pushup(rt);
    return rt;
}
void dfs(int x){
    for (int i=1;i<=17;i++){
        if (dep[x]-(1<<i)<0) break;
        f[x][i]=f[f[x][i-1]][i-1];
    }
    for (int i=head[x];i;i=nxt[i]){
        f[bian[i]][0]=x,dep[bian[i]]=dep[x]+1;
        dfs(bian[i]);
        root[x]=merge(root[x],root[bian[i]]);
    }
}
int query(int rt,int l,int r,int x,int y){
    if(x<=l&&r<=y)return sum[rt];
    int ans=0;
    if(x<=mid)ans+=query(lson[rt],l,mid,x,y);
    if(y>mid)ans+=query(rson[rt],mid+1,r,x,y);
    return ans;
}
bool check(int k,int left,int right,int x){
    if(k==0)return 1;
    for(int rt=17;rt>=0;rt--)
        if(l[f[x][rt]]>=k)x=f[x][rt];
    return query(root[x],1,n,left,right);
}
int main(){
    cin>>n>>m;
    cin>>s+1;reverse(s+1,s+n+1);
    last=++cnt;
    for(int i=1;i<=n;++i)ins(i);
    for(int i=1;i<=cnt;++i)add(fa[i],i);
    for(int i=1;i<=cnt;++i) 
        if(mp[i])root[i]=updata(root[i],1,n,mp[i]);
    dep[1]=1; dfs(1); 
    for (int i=1;i<=m;++i){
        int a,b,c,d;
        scanf("%d%d%d%d",&a,&b,&c,&d);
        swap(a,b); swap(c,d);
        a=n-a+1; b=n-b+1; c=n-c+1; d=n-d+1;
        int l=0,r=min(d-c+1,b-a+1),ans=0;
        while (l<=r){
            if (check(mid,a+mid-1,b,pos[d])) ans=max(ans,mid),l=mid+1;
            else r=mid-1;
        } 
        printf("%d\n",ans);
    }
} 
posted @ 2018-12-10 20:33  整理者  阅读(263)  评论(1编辑  收藏  举报