BZOJ4556 [Tjoi2016&Heoi2016]字符串 【后缀数组 + 主席树 + 二分 + ST表】

本文介绍了一种使用二分答案结合主席树实现的高效算法,用于解决字符串匹配问题中的最长公共前缀查询。通过高度预处理和区间查询优化,该算法能在大量数据下保持良好的性能。

题目

佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了
一个长为n的字符串s,和m个问题。佳媛姐姐必须正确回答这m个问题,才能打开箱子拿到礼物,升职加薪,出任CE
O,嫁给高富帅,走上人生巅峰。每个问题均有a,b,c,d四个参数,问你子串s[a..b]的所有子串和s[c..d]的最长公
共前缀的长度的最大值是多少?佳媛姐姐并不擅长做这样的问题,所以她向你求助,你该如何帮助她呢?

输入格式

输入的第一行有两个正整数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

输出格式

对于每一次询问,输出答案。

输入样例

5 5

aaaaa

1 1 1 5

1 5 1 1

2 3 2 3

2 4 2 3

2 3 2 4

输出样例

1

1

2

2

2

题解

一开始看错题,,原来是要求s[a...b]所有子串和子串s[c...d]的最大LCP【注意s[c...d]只是一个字符串】
那么我们二分一下答案
就需要快速判断子串c的height分组中是否存在子串s[a...b]
这个拿主席树记录位置存在信息就可以做到
height分组的边界可以倍增用ST表判断
还要注意不要越过子串的边界

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long int
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<' '; puts("");
using namespace std;
const int maxn = 200005,maxm = 6000005,INF = 1000000000;
inline int read(){
    int out = 0,flag = 1; char c = getchar();
    while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
    while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
    return out * flag;
}
int n,q;
char s[maxn];
int sa[maxn],rank[maxn],height[maxn],t1[maxn],t2[maxn],bac[maxn],m;
void getSA(){
    int *x = t1,*y = t2; m = 256;
    for (int i = 0; i <= m; i++) bac[i] = 0;
    for (int i = 1; i <= n; i++) bac[x[i] = s[i]]++;
    for (int i = 1; i <= m; i++) bac[i] += bac[i - 1];
    for (int i = n; i; i--) sa[bac[x[i]]--] = i;
    for (int k = 1; k <= n; k <<= 1){
        int p = 0;
        for (int i = n - k + 1; i <= n; i++) y[++p] = i;
        for (int i = 1; i <= n; i++) if (sa[i] - k > 0) y[++p] = sa[i] - k;
        for (int i = 0; i <= m; i++) bac[i] = 0;
        for (int i = 1; i <= n; i++) bac[x[y[i]]]++;
        for (int i = 1; i <= m; i++) bac[i] += bac[i - 1];
        for (int i = n; i; i--) sa[bac[x[y[i]]]--] = y[i];
        swap(x,y);
        p = x[sa[1]] = 1;
        for (int i = 2; i <= n; i++)
            x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k] ? p : ++p);
        if (p >= n) break;
        m = p;
    }
    for (int i = 1; i <= n; i++) rank[sa[i]] = i;
    for (int i = 1,k = 0; i <= n; i++){
        if (k) k--;
        int j = sa[rank[i] - 1];
        while (s[i + k] == s[j + k]) k++;
        height[rank[i]] = k;
    }
}
int sum[maxm],ls[maxm],rs[maxm],rt[maxn],cnt;
void add(int& u,int pre,int l,int r,int pos){
    sum[u = ++cnt] = sum[pre] + 1;
    ls[u] = ls[pre]; rs[u] = rs[pre];
    if (l == r) return;
    int mid = l + r >> 1;
    if (mid >= pos) add(ls[u],ls[pre],l,mid,pos);
    else add(rs[u],rs[pre],mid + 1,r,pos);
}
int query(int u,int v,int l,int r,int L,int R){
    if (l >= L && r <= R) return sum[u] - sum[v];
    int mid = l + r >> 1;
    if (mid >= R) return query(ls[u],ls[v],l,mid,L,R);
    if (mid < L) return query(rs[u],rs[v],mid + 1,r,L,R);
    return query(ls[u],ls[v],l,mid,L,R) + query(rs[u],rs[v],mid + 1,r,L,R);
}
int a,b,c,d;
int bin[maxn],Log[maxn],mn[maxn][20];
void build(){
    for (int i = 1; i <= n; i++) add(rt[i],rt[i - 1],1,n,sa[i]);
    bin[0] = 1;
    for (int i = 1; i <= 20; i++) bin[i] = bin[i - 1] << 1;
    Log[0] = -1;
    for (int i = 1; i < maxn; i++) Log[i] = Log[i >> 1] + 1;
    for (int i = 1; i <= n; i++) mn[i][0] = height[i];
    for (int i = 1; i <= 17; i++)
        for (int j = 1; j <= n; j++){
            if (j + bin[i] - 1 > n) break;
            mn[j][i] = min(mn[j][i - 1],mn[j + bin[i - 1]][i - 1]);
        }
}
int getm(int l,int r){
    if (l > r) return INF;
    int t = Log[r - l + 1];
    return min(mn[l][t],mn[r - bin[t] + 1][t]);
}
bool check(int len){
    if (len == 0) return true;
    int u = rank[c],tmp,l = u,r = u;
    for (int i = 17; i >= 0; i--){
        tmp = l - bin[i];
        if (tmp > 0 && getm(tmp + 1,u) >= len) l = tmp;
        tmp = r + bin[i];
        if (tmp <= n && getm(u + 1,tmp) >= len) r = tmp;
    }
    return query(rt[r],rt[l - 1],1,n,a,b - len + 1);
}
void solve(){
    while (q--){
        a = read(); b = read();
        c = read(); d = read();
        int l = 0,r = min(d - c + 1,b - a + 1),mid;
        while (l < r){
            mid = l + r + 1 >> 1;
            if (check(mid)) l = mid;
            else r = mid - 1;
        }
        printf("%d\n",l);
    }
}
int main(){
    //freopen("in.in","r",stdin);
    n = read(); q = read();
    scanf("%s",s + 1);
    getSA();
    build();
    solve();
    return 0;
}

转载于:https://www.cnblogs.com/Mychael/p/8804865.html

### 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、付费专栏及课程。

余额充值