[BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)

题目描述

给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border 。

Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+1..|s|], |s| 为 s 的长度。

题解

这题的描述很短,给人一种很可做的假象。

暴力1:每次对区间lr做一次KMP,求出border数组,复杂度nq。

暴力2:构建后缀自动机,用线段树合并维护出right集合考虑到两个串的最长后缀为他们在parent树上的LCA的len,所以我们可以在parent树上跳father,相当于枚举LCA,那么如果有匹配的点,则一定满足:

i-len[LCA]+1<=l => i=len[LCA]<l => i<l+len[LCA]       i>=l&&i<r (i为我们要求的点,l为询问的左端点,LCA为i和r的LCA)

所以我们只需要在线段树上查子树内查询满足上述条件的最大的i就可以了,复杂度最好qlogn,最差qn。

这两种暴力好像差不多。。

我们观察到第二种暴力它的瓶颈在于枚举LCA,但查询只需要一个log,我们可以想一些办法把复杂度均摊一下。

链分治

这就是这道题的重头戏,它用到了一个重要的性质,我们将一棵树重链剖分之后,从根到任意一点的路径上,轻重链切换的次数是不超过log的。

然后我们就可以用它搞一些事情。

比如说这道题,我们可以把询问拆成log个挂在它到根的路径上,每条链挂一个,对于当前链,挂在原来的点上,对于上面的链,挂在链的最底端。

其实我们也是相当于在枚举lca,对于挂上去的点,我们可以直接用上面暴力2的方法统计答案,复杂度是log的,那么对于其他点,我们如何统计答案呢?

我们可以发现一个性质,就是对于最底端这个点向上的其他链上的点作为LCA时,答案只会出现在这个点除了重儿子以外的子树内以及自己,因为重链底下的子树我们刚才已经处理过了。

所以我们的做法就是,对于每一条重链,自顶至底处理,然后我们把式子移个项

i-len[LCA]<l

我们把这个点的所有非重儿子所在子树中的点的i-len[LCA]全部加进以i为下标的线段树中,然后一直往下合并,那么每到一个点,它就保存了它到重链顶端所有LCA的信息,然后我们直接询问挂在这个点的询问就可以了。

但每次暴力加的复杂度对吗?

树上启发式合并(DSU on tree)

这个操作的原理和上面的一模一样,都是利用了轻重链log的性质。

主要思想就是,维护重链,轻链暴力,考虑一个点会被暴力做多少次,它到根的路径上轻重链切换的时候才会产生一次,根据上面的结论,这是log的。

于是我们在nlog2的时间内做完了这道题。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 400002
#define inf 2e9
using namespace std;
vector<int>vec[N];
char s[N];
int last,cnt,n,tot,head[N],topp,ls[N*30],rs[N*30],tr[N*30],T[N<<1],len[N],id[N],ch[N][26],fa[N],f[N];
int L[N*30],R[N*30],size[N],top[N],son[N],ans[N],q,rt[N],_id[N],mi[N*30];
inline int rd(){
    int x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c==‘-‘)f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
struct edge{int n,to;}e[N<<1];
struct node{int l,r;}qu[N];
inline void add(int u,int v){e[++tot].n=head[u];e[tot].to=v;head[u]=tot;}
inline int newnode(){
    int x=++topp;tr[x]=inf;ls[x]=rs[x]=0;
    return x;
}
void ins(int &cnt,int l,int r,int x){
    if(!cnt)cnt=++topp,mi[cnt]=inf;mi[cnt]=min(mi[cnt],x);
    if(l==r)return;
    int mid=(l+r)>>1;
    if(mid>=x)ins(L[cnt],l,mid,x);
    else ins(R[cnt],mid+1,r,x);
}
int merge(int cnt,int pre,int l,int r){
    if(!cnt||!pre)return cnt^pre;
    int p=++topp;mi[p]=min(mi[cnt],mi[pre]);
    if(l==r)return p;
    int mid=(l+r)>>1;
    L[p]=merge(L[cnt],L[pre],l,mid);
    R[p]=merge(R[cnt],R[pre],mid+1,r);
    return p;
}
int prequery(int cnt,int l,int r,int ql,int qr,int x){
    if(mi[cnt]>=x)return 0;
    if(l==r)return (l>=ql&&l<=qr)?l:0;
    int mid=(l+r)>>1;
    if(mid<qr&&mi[R[cnt]]<x){
        int num=prequery(R[cnt],mid+1,r,ql,qr,x);
        if(num)return num;
    }
    else if(mid>=ql&&mi[L[cnt]]<x)return prequery(L[cnt],l,mid,ql,qr,x);
    return 0;
}
inline void insert(int x,int tag){
    int p=last,np=++cnt;last=np;len[np]=len[p]+1;
    ins(T[np],1,n,tag);id[tag]=np;_id[np]=tag;
    for(;p&&!ch[p][x];p=fa[p])ch[p][x]=np;
    if(!p)fa[np]=1;
    else{
        int q=ch[p][x];
        if(len[p]+1==len[q])fa[np]=q;
        else{
            int nq=++cnt;len[nq]=len[p]+1;
            memcpy(ch[nq],ch[q],sizeof(ch[nq]));
            fa[nq]=fa[q];fa[q]=fa[np]=nq;
            for(;ch[p][x]==q;p=fa[p])ch[p][x]=nq;
        }
    }
}
void ins2(int &cnt,int l,int r,int x,int y){
    if(!cnt)cnt=newnode();tr[cnt]=min(tr[cnt],y);
    if(l==r)return;
    int mid=(l+r)>>1;
    if(mid>=x)ins2(ls[cnt],l,mid,x,y);
    else ins2(rs[cnt],mid+1,r,x,y);
}
int merge2(int cnt,int pre,int l,int r){
    if(!cnt||!pre)return cnt^pre;
    int mid=(l+r)>>1;tr[cnt]=min(tr[cnt],tr[pre]);
    if(l==r)return cnt;
    ls[cnt]=merge2(ls[cnt],ls[pre],l,mid);
    rs[cnt]=merge2(rs[cnt],rs[pre],mid+1,r);
    return cnt;
}
int query(int cnt,int l,int r,int L,int R,int x){   ///!!!
    if(l==r)return (l>=L&&l<=R)?l:0;
    if(tr[cnt]>=x)return 0;
    int mid=(l+r)>>1;
    if(mid<R&&tr[rs[cnt]]<x){
        int num=query(rs[cnt],mid+1,r,L,R,x);
        if(num)return num;
    }
    if(tr[ls[cnt]]<x&&mid>=L)return query(ls[cnt],l,mid,L,R,x);
    return 0;
}
void dfs1(int u){
    size[u]=1;
    for(int i=head[u];i;i=e[i].n){
        int v=e[i].to;
        f[v]=u;dfs1(v);
        size[u]+=size[v];
        if(size[v]>size[son[u]])son[u]=v;
    }
}
void dfs2(int u){
    if(!top[u])top[u]=u;if(son[u])top[son[u]]=top[u],dfs2(son[u]);
    for(int i=head[u];i;i=e[i].n){
       if(e[i].to!=son[u])dfs2(e[i].to);
       T[u]=merge(T[u],T[e[i].to],1,n);
    }
}
void prework(int cnt,int l,int r,int top){
    if(l==r){ins2(rt[top],1,n,l,l-len[top]);return;}
    int mid=(l+r)>>1;
    if(L[cnt])prework(L[cnt],l,mid,top);
    if(R[cnt])prework(R[cnt],mid+1,r,top);
}
void solve(int u){
    for(int i=head[u];i;i=e[i].n)solve(e[i].to);
    if(top[u]==u){
        int x=u;
        while(x){
            for(int i=head[x];i;i=e[i].n){
              int v=e[i].to;if(v==son[x])continue;
              prework(T[v],1,n,x);
            }
            for(int i=0;i<vec[x].size();++i){
                int l=qu[vec[x][i]].l,r=qu[vec[x][i]].r;
                ans[vec[x][i]]=max(ans[vec[x][i]],query(rt[x],1,n,l,r-1,l));
            }
            if(son[x])rt[son[x]]=merge2(rt[son[x]],rt[x],1,n);
            x=son[x];
        }
    }
}
int main(){
    tr[0]=mi[0]=inf;
    scanf("%s",s+1);n=strlen(s+1);
    cnt=last=1;
    for(int i=1;i<=n;++i)insert(s[i]-‘a‘,i);
    for(int i=2;i<=cnt;++i)add(fa[i],i);
    dfs1(1);dfs2(1);
    q=rd();topp=0;//合并完后的第一类线段树不会新增节点了,所以把它清空。
    for(int i=1;i<=q;++i){
        qu[i].l=rd();qu[i].r=rd();int x=id[qu[i].r];
        while(x){
          vec[x].push_back(i);
          ans[i]=max(ans[i],prequery(T[x],1,n,qu[i].l,qu[i].r-1,qu[i].l+len[x]));//!!!
          x=f[top[x]];
        }
    }
    for(int i=1;i<=n;++i)ins2(rt[id[i]],1,n,i,0);
    solve(1);
    for(int i=1;i<=q;++i)printf("%d\n",ans[i]?ans[i]-qu[i].l+1:0);
    return 0;
}
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 400002
#define inf 2e9
using namespace std;
vector<int>vec[N];
char s[N];
int last,cnt,n,tot,head[N],topp,ls[N*30],rs[N*30],tr[N*30],T[N<<1],len[N],id[N],ch[N][26],fa[N],f[N];
int L[N*30],R[N*30],size[N],top[N],son[N],ans[N],q,rt[N],_id[N],mi[N*30];
inline int rd(){
    int x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c==‘-‘)f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
struct edge{int n,to;}e[N<<1];
struct node{int l,r;}qu[N];
inline void add(int u,int v){e[++tot].n=head[u];e[tot].to=v;head[u]=tot;}
inline int newnode(){
    int x=++topp;tr[x]=inf;ls[x]=rs[x]=0;
    return x;
}
void ins(int &cnt,int l,int r,int x){
    if(!cnt)cnt=++topp,mi[cnt]=inf;mi[cnt]=min(mi[cnt],x);
    if(l==r)return;
    int mid=(l+r)>>1;
    if(mid>=x)ins(L[cnt],l,mid,x);
    else ins(R[cnt],mid+1,r,x);
}
int merge(int cnt,int pre,int l,int r){
    if(!cnt||!pre)return cnt^pre;
    int p=++topp;mi[p]=min(mi[cnt],mi[pre]);
    if(l==r)return p;
    int mid=(l+r)>>1;
    L[p]=merge(L[cnt],L[pre],l,mid);
    R[p]=merge(R[cnt],R[pre],mid+1,r);
    return p;
}
int prequery(int cnt,int l,int r,int ql,int qr,int x){
    if(mi[cnt]>=x)return 0;
    if(l==r)return (l>=ql&&l<=qr)?l:0;
    int mid=(l+r)>>1;
    if(mid<qr&&mi[R[cnt]]<x){
        int num=prequery(R[cnt],mid+1,r,ql,qr,x);
        if(num)return num;
    }
    else if(mid>=ql&&mi[L[cnt]]<x)return prequery(L[cnt],l,mid,ql,qr,x);
    return 0;
}
inline void insert(int x,int tag){
    int p=last,np=++cnt;last=np;len[np]=len[p]+1;
    ins(T[np],1,n,tag);id[tag]=np;_id[np]=tag;
    for(;p&&!ch[p][x];p=fa[p])ch[p][x]=np;
    if(!p)fa[np]=1;
    else{
        int q=ch[p][x];
        if(len[p]+1==len[q])fa[np]=q;
        else{
            int nq=++cnt;len[nq]=len[p]+1;
            memcpy(ch[nq],ch[q],sizeof(ch[nq]));
            fa[nq]=fa[q];fa[q]=fa[np]=nq;
            for(;ch[p][x]==q;p=fa[p])ch[p][x]=nq;
        }
    }
}
void ins2(int &cnt,int l,int r,int x,int y){
    if(!cnt)cnt=newnode();tr[cnt]=min(tr[cnt],y);
    if(l==r)return;
    int mid=(l+r)>>1;
    if(mid>=x)ins2(ls[cnt],l,mid,x,y);
    else ins2(rs[cnt],mid+1,r,x,y);
}
int merge2(int cnt,int pre,int l,int r){
    if(!cnt||!pre)return cnt^pre;
    int mid=(l+r)>>1;tr[cnt]=min(tr[cnt],tr[pre]);
    if(l==r)return cnt;
    ls[cnt]=merge2(ls[cnt],ls[pre],l,mid);
    rs[cnt]=merge2(rs[cnt],rs[pre],mid+1,r);
    return cnt;
}
int query(int cnt,int l,int r,int L,int R,int x){   ///!!!
    if(l==r)return (l>=L&&l<=R)?l:0;
    if(tr[cnt]>=x)return 0;
    int mid=(l+r)>>1;
    if(mid<R&&tr[rs[cnt]]<x){
        int num=query(rs[cnt],mid+1,r,L,R,x);
        if(num)return num;
    }
    if(tr[ls[cnt]]<x&&mid>=L)return query(ls[cnt],l,mid,L,R,x);
    return 0;
}
void dfs1(int u){
    size[u]=1;
    for(int i=head[u];i;i=e[i].n){
        int v=e[i].to;
        f[v]=u;dfs1(v);
        size[u]+=size[v];
        if(size[v]>size[son[u]])son[u]=v;
    }
}
void dfs2(int u){
    if(!top[u])top[u]=u;if(son[u])top[son[u]]=top[u],dfs2(son[u]);
    for(int i=head[u];i;i=e[i].n){
       if(e[i].to!=son[u])dfs2(e[i].to);
       T[u]=merge(T[u],T[e[i].to],1,n);
    }
}
void prework(int cnt,int l,int r,int top){
    if(l==r){ins2(rt[top],1,n,l,l-len[top]);return;}
    int mid=(l+r)>>1;
    if(L[cnt])prework(L[cnt],l,mid,top);
    if(R[cnt])prework(R[cnt],mid+1,r,top);
}
void solve(int u){
    for(int i=head[u];i;i=e[i].n)solve(e[i].to);
    if(top[u]==u){
        int x=u;
        while(x){
            for(int i=head[x];i;i=e[i].n){
              int v=e[i].to;if(v==son[x])continue;
              prework(T[v],1,n,x);
            }
            for(int i=0;i<vec[x].size();++i){
                int l=qu[vec[x][i]].l,r=qu[vec[x][i]].r;
                ans[vec[x][i]]=max(ans[vec[x][i]],query(rt[x],1,n,l,r-1,l));
            }
            if(son[x])rt[son[x]]=merge2(rt[son[x]],rt[x],1,n);
            x=son[x];
        }
    }
}
int main(){
    tr[0]=mi[0]=inf;
    scanf("%s",s+1);n=strlen(s+1);
    cnt=last=1;
    for(int i=1;i<=n;++i)insert(s[i]-‘a‘,i);
    for(int i=2;i<=cnt;++i)add(fa[i],i);
    dfs1(1);dfs2(1);
    q=rd();topp=0;//合并完后的第一类线段树不会新增节点了,所以把它清空。
    for(int i=1;i<=q;++i){
        qu[i].l=rd();qu[i].r=rd();int x=id[qu[i].r];
        while(x){
          vec[x].push_back(i);
          ans[i]=max(ans[i],prequery(T[x],1,n,qu[i].l,qu[i].r-1,qu[i].l+len[x]));//!!!
          x=f[top[x]];
        }
    }
    for(int i=1;i<=n;++i)ins2(rt[id[i]],1,n,i,0);
    solve(1);
    for(int i=1;i<=q;++i)printf("%d\n",ans[i]?ans[i]-qu[i].l+1:0);
    return 0;
}

原文地址:https://www.cnblogs.com/ZH-comld/p/10162320.html

时间: 2024-11-09 01:49:27

[BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)的相关文章

【codeforces666E】Forensic Examination 广义后缀自动机+树上倍增+线段树合并

题目描述 给出 $S$ 串和 $m$ 个 $T_i$ 串,$q$ 次询问,每次询问给出 $l$ .$r$ .$x$ .$y$ ,求 $S_{x...y}$ 在 $T_l,T_{l+1},...,T_r$ 中的哪一个里出现次数最多,输出出现次数最多的串编号(如果有多个则输出编号最小的)以及相应出现次数. $|S|,q\le 5\times 10^5$ ,$\sum\limits_{i=1}^m|T_i|\le 5\times 10^4$ . 题解 广义后缀自动机+树上倍增+线段树合并 对 $S$

[BJWC2018]Border 的四种求法

description luogu 给一个小写字母字符串\(S\),\(q\)次询问每次给出\(l,r\),求\(s[l..r]\)的\(Border\). solution 我们考虑转化题面:给定\(l,r\),求满足\(lcs(i,r)\ge i-l+1\)的最大的\(i\). 对于\(lcs(i,r)\),考虑对\(S\)构建\(SAM\),那么我们知道\(lcs\)的可能取值就是从后缀\(r\)所代表的节点沿着\(fail\)树到根节点的路径上节点的\(len\)的取值. 那么我们得到了

CF1037H Security 后缀自动机 + right集合线段树合并 + 贪心

后缀自动机 + 线段树合并的裸题. 我这种大菜逼都秒切的题目,一定是送分题. #include<bits/stdc++.h> #define setIO(s) freopen(s".in","r",stdin) #define maxn 220000 using namespace std; int n; int rt[maxn]; namespace tr { #define mid ((l+r)>>1) #define lson t[x]

CF700E Cool Slogans 后缀自动机 + right集合线段树合并 + 树形DP

又是一道 SAM 神题. Code: #include<bits/stdc++.h> #define maxn 400002 #define setIO(s) freopen(s".in","r",stdin) using namespace std; namespace tr { #define lson t[x].l #define rson t[x].r #define mid ((l+r)>>1) int cnt; struct No

CF666E Forensic Examination(后缀自动机+线段树合并)

给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[pl..pr]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 我们首先对m个字符串数组建出后缀自动机,然后我们可以通过跳trans边找到S前i个字符代表的前缀的最长后缀.我们要找的是S[pl..pr]并不是以pr结束最长的后缀,但我们可以确定S[pl..pr]一定是当前点的祖先所以当我们跳到pr代表的点时我们倍增往上跳知道找到一个点的长度刚好大于等于pr-pl+1,这个点就是询问

HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)

题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点,求第k大的下标即可. 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10,mod=998244353; 5 char buf[N]; 6 int s[N],sa[

CF700E:Cool Slogans(后缀自动机,线段树合并)

Description 给你一个字符串,如果一个串包含两个不重叠的相同子串,那么这个串的价值就是子串的价值+1.问你给定字符串的最大价值子串的价值. Input 第一行读入字符串长度$n$,第二行是字符串. Output 一行答案. Sample Input1 3abc Sample Output1 1 Sample Input2 5ddddd Sample Output2 5 Sample Input3 11abracadabra Sample Output3 3 Solution 首先把后缀

【XSY1551】往事 广义后缀数组 线段树合并

题目大意 给你一颗trie树,令\(s_i\)为点\(i\)到根的路径上的字符组成的字符串.求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\) \(LCP=\)最长公共前缀,\(LCS=\)最长公共后缀 \(1\leq n\leq 200000\),字符集为\(\{0\ldots 300\}\) 题解 我们先看看这个\(LCP(s_u,s_v)\)怎么求 广义后缀自动机不行.广义后缀树可能可以,但我不会.广义后缀数组可以.然后我就开始手推广义后缀数组 广义

CF.666E.Forensic Examination(广义后缀自动机 线段树合并)

题目链接 \(Description\) 给定串S和m个串Ti.Q次询问,每次询问l,r,pl,pr,求S[pl~pr]在Tl~Tr中的哪个串出现次数最多,输出最多次数及其T的下标.若有多个,输出下标最小的. \(Solution\) 挺好的题吧 对T个串建SAM,然后要求出SAM每个节点上|right|最大的是哪个串. 每个节点的|right|可以在DFS parent树时合并子节点得到,如果用线段树维护,|right|最大的位置也可以合并得到. 这样可以离线处理询问,最后DFS一遍得到答案.