[bzoj4556][TJOI&&HEOI2016]字符串

本文介绍了一种解决特定字符串问题的方法,该问题涉及寻找两个子串间最长公共前缀的最大长度。通过使用主席树和二分查找等高级数据结构和技术,文章详细阐述了如何有效地解决这一挑战。

4556: [Tjoi2016&Heoi2016]字符串

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

开始想了一种O(logn)(但是有些问题),大致是这样的。
可以发现在求lcp的时候用的是区间最小值,这样假如当求一个位置的最大lcp的时候,那肯定是找这个位置rank的前驱和后继是最优的,因为再往前找的话答案是不增的。
想到这点之后就直接写了个主席树。
主席树中可以存区间的元素个数和,查询的时候先查询一下区间和,再在区间中像平衡树那样看看需要往哪边走,就能logn的找到前驱后继了。
但是这样直接找前驱后继会有个问题,比如这组数据:

5 1
aaaab
1 2 3 5

这组数据在找后继的时候找的是aaab这个后缀,但是由于这个有区间长度的限制,所以最后找到的答案是1。但是更优的方案是找aaaab这个后缀,这样就可以得到2这个答案。
为了解决这个问题,就需要在外面套个二分答案,这个二分就相当于在选了aaab这个后缀之后,可以继续往后选。也就相当于是二分的可选择区间的右端点。
时间复杂度是O(mlog2n)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010;
const int M=4000000;
int n,m,o,s[N],t1[N],t2[N],c[N],sa[N],rank[N],height[N],st[N][20],Log[N],root[N],l[M],r[M],sum[M],siz;
inline int in(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
inline bool cmp(int *y,int p,int q,int k){
    int o0=(p+k>=n)?-1:y[p+k];
    int o1=(q+k>=n)?-1:y[q+k];
    return o0==o1&&y[p]==y[q];
}
inline void build_sa(){
    int i,k,p,*x=t1,*y=t2;
    for(o=27,i=0;i<o;++i) c[i]=0;
    for(i=0;i<n;++i) ++c[x[i]=s[i]];
    for(i=1;i<o;++i) c[i]+=c[i-1];
    for(i=n-1;~i;--i) sa[--c[x[i]]]=i;
    for(k=1;k<=n;k<<=1){
        for(p=0,i=n-k;i<n;++i) y[p++]=i;
        for(i=0;i<n;++i) if(sa[i]>=k) y[p++]=sa[i]-k;
        for(i=0;i<o;++i) c[i]=0;
        for(i=0;i<n;++i) ++c[x[y[i]]];
        for(i=1;i<o;++i) c[i]+=c[i-1];
        for(i=n-1;~i;--i) sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        x[sa[0]]=0;o=1;
        for(i=1;i<n;++i) x[sa[i]]=cmp(y,sa[i],sa[i-1],k)?o-1:o++;
        if(o>=n) break;
    }
}
inline void build_height(){
    int i,k=0,j;
    for(i=0;i<n;++i) rank[sa[i]]=i;
    for(i=0;i<n;++i){
        if(!rank[i]) continue;
        k=k?--k:k;
        j=sa[rank[i]-1];
        while(s[j+k]==s[i+k]) ++k;
        height[rank[i]]=k;
    }
    memset(st,127/3,sizeof(st));    
    for(i=0;i<n;++i) st[i][0]=height[i];
    for(j=1;j<=20;++j)
      for(i=0;i+(1<<(j-1))<n;++i)
        st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
    for(j=0,i=1;i<=n;++i){
        if((1<<(j+1))<=i) ++j;
        Log[i]=j;
    }
}
inline int LCP(int x,int y){
    if(x>y) swap(x,y);
    int k=Log[y-x];++x;
    return min(st[x][k],st[y-(1<<k)+1][k]);   
}
#define mid (L+R)/2
inline void insert(int L,int R,int x,int &y,int z){
    y=++siz;
    sum[y]=sum[x]+1;
    if(L>=R) return ;
    l[y]=l[x];r[y]=r[x];
    if(z<=mid) insert(L,mid,l[x],l[y],z);
    else insert(mid+1,R,r[x],r[y],z);
}
inline int query_sum(int L,int R,int x,int y,int left,int right){
    int now=0;
    if(y<x) return 0;
    if(left<=L&&right>=R) return sum[y]-sum[x];
    if(left<=mid) now+=query_sum(L,mid,l[x],l[y],left,right);
    if(right>mid) now+=query_sum(mid+1,R,r[x],r[y],left,right);
    return now;
}
inline int query_pre(int L,int R,int x,int y,int left,int right,int now){
    if(L==R) return L;
    if(sum[l[y]]-sum[l[x]]>=now) return query_pre(L,mid,l[x],l[y],left,right,now);
    else return query_pre(mid+1,R,r[x],r[y],left,right,now-(sum[l[y]]-sum[l[x]]));
}
inline int query_sub(int L,int R,int x,int y,int left,int right,int now){
    if(L==R) return L;
    if(sum[r[y]]-sum[r[x]]>=now) return query_sub(mid+1,R,r[x],r[y],left,right,now);
    else return query_sub(L,mid,l[x],l[y],left,right,now-(sum[r[y]]-sum[r[x]]));
}
int main(){
    int i,now,left,right;
    scanf("%d%d",&n,&m);
    for(i=0;i<n;++i){
        char ch=getchar();
        while(ch<'a'||ch>'z') ch=getchar();
        s[i]=ch-'a'+1;
    }
    build_sa();
    build_height();
    for(i=0;i<n;++i) insert(0,n,root[i],root[i+1],rank[i]);
    while(m--){
        int aa,bb,cc,dd;
        aa=in();bb=in();cc=in();dd=in();
        int L=0,R=bb-aa+1,maxn=0;
        while(L<R){
            now=query_sum(0,n,root[aa-1],root[bb-mid],0,rank[cc-1]-1);
            left=now?query_pre(0,n,root[aa-1],root[bb-mid],0,rank[cc-1],now):-1;
            now=query_sum(0,n,root[aa-1],root[bb-mid],rank[cc-1]+1,n);
            right=now?query_sub(0,n,root[aa-1],root[bb-mid],rank[cc-1],n,now):-1;
            int ans=0;
            if(left!=-1){
                int oo=min(LCP(left,rank[cc-1]),bb-sa[left]);
                ans=max(ans,min(dd-cc+1,oo));
            }
            if(right!=-1){
                int oo=min(LCP(rank[cc-1],right),bb-sa[right]);
                ans=max(ans,min(dd-cc+1,oo));
            }
            if(cc>=aa&&cc<=bb) ans=max(ans,min(dd,bb)-cc+1);
            maxn=max(maxn,ans);
            if(ans>=mid) L=mid+1;
            else R=mid;
        }
        printf("%d\n",maxn);
    }
}
### BZOJ1461 字符串匹配 题解 针对BZOJ1461字符串匹配问题,解决方法涉及到了KMP算法以及树状数组的应用。对于此类问题,朴素的算法无法满足时间效率的要求,因为其复杂度可能高达O(ML²),其中M代表模式串的数量,L为平均长度[^2]。 为了提高效率,在这个问题中采用了更先进的技术组合——即利用KMP算法来预处理模式串,并通过构建失配树(也称为失败指针),使得可以在主串上高效地滑动窗口并检测多个模式串的存在情况。具体来说: - **前缀函数与KMP准备阶段**:先对每一个给定的模式串执行一次KMP算法中的pre_kmp操作,得到各个模式串对应的next数组。 - **建立失配树结构**:基于所有模式串共同构成的一棵Trie树基础上进一步扩展成带有失配链接指向的AC自动机形式;当遇到某个节点不存在对应字符转移路径时,则沿用该处失配链路直至找到合适的目标或者回到根部重新开始尝试其他分支。 - **查询过程**:遍历整个待查文本序列的同时维护当前状态处于哪一层级下的哪个子结点之中,每当成功匹配到完整的单词就更新计数值至相应位置上的f_i变量里去记录下这一事实。 下面是简化版Python代码片段用于说明上述逻辑框架: ```python from collections import defaultdict def build_ac_automaton(patterns): trie = {} fail = [None]*len(patterns) # 构建 Trie 树 for i,pattern in enumerate(patterns): node = trie for char in pattern: if char not in node: node[char]={} node=node[char] node['#']=i queue=[trie] while queue: current=queue.pop() for key,value in list(current.items()): if isinstance(value,int):continue if key=='#': continue parent=current[key] p=fail[current is trie and 0 or id(current)] while True: next_p=p and p.get(key,None) if next_p:break elif p==0: value['fail']=trie break else:p=fail[id(p)] if 'fail'not in value:value['fail']=next_p queue.append(parent) return trie,fail def solve(text, patterns): n=len(text) m=len(patterns) f=[defaultdict(int)for _in range(n)] ac_trie,_=build_ac_automaton(patterns) state=ac_trie for idx,char in enumerate(text+'$',start=-1): while True: trans=state.get(char,state.get('#',{}).get('fail')) if trans!=None: state=trans break elif '#'in state: state[state['#']['fail']] else: state=ac_trie cur_state=state while cur_state!={}and'#'in cur_state: matched_pattern_idx=cur_state['#'] f[idx][matched_pattern_idx]+=1 cur_state=cur_state['fail'] result=[] for i in range(len(f)-1): row=list(f[i].values()) if any(row): result.extend([sum((row[:j+1]))for j,x in enumerate(row[::-1])if x>0]) return sum(result) patterns=["ab","bc"] text="abc" print(solve(text,text)) #[^4] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值