「BJWC2018」Border 的四种求法

「BJWC2018」Border 的四种求法

题目描述

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

\(1 \leq n,q \leq 10^5\)

解题思路 :

求 Border 等价于在 \([l, r)\) 中找一个点 \(i\) ,满足 \(lcs(i, r) \geq i -l + 1\) ,且 \(i\) 最大。

考虑把问题丢到 \(\text{Sam}\) 上,那么满足的条件变为 \(len(lca(i,r)) > i - l\) ,对于 \(r\) 的每一个祖先算出其作为 \(lca\) 时最优的 \(i\) ,可以对 \(\text{parent}\) 树进行轻重链剖分,对于重链和轻重链切换时候的贡献分类讨论。

对于重链上的点,其要作为 \(lca\) 的话 \(i\) 一定是其本身或者一个来自轻子树里面的点,于是可以暴力把每一个点所连出去的轻子树的点的贡献加在这个点上,考虑一个点被加的次数等同于轻重链切换的次数,复杂度没有问题。

考虑把重链上的点的贡献并在一起后怎么求这条重链上能得到的最大的 \(i\) ,将条件移一下项转变为 \(i-len(lca(i,r))<l\) ,那么问题就变为找出 \(i-len(x) < l\)\(i < r\) 最大的 \(i\) 。其中 \(x\) 是重链上的一个点,\(i\) 是其轻子树里面的一个点。仔细观察发现这个东西可以对于每一条重链维护一颗以 \(i\) 为下标的线段树,直接在线段树上二分即可。

考虑轻重链切换的情况,对于跳上去新的重链的第一个点 \(x\),其除了之前那条重链所在的子树的点都可以和 \(r\) 产生 \(lca(r, i)=x\) ,这里直接统计的话复杂度就爆炸了。但是可以观察到,对于之前那条重链所在的点,必然会在之前的某个点 \(y\) 被算到过,而 \(\text{parent}\) 树上 \(len\) 是随深度递增的,如果在 \(x\) 这里再被算一次不会产生更优的答案。所以可以直接统计子树里面的所有情况,用再对每一个点开一个线段树并用线段树合并维护即可

实际上一条重链并不是所有点都会产生贡献,只有当前跳到的点 \(u\) 以上的点的贡献才能被算入答案。所以把询问离线下来拆成若干段重链的形式。最后 \(\text{dfs}\) 一遍边加贡献边回答每一段的询问,最后合并每一段的答案求出最终的结果。总复杂度 \(O(nlog^2n)\)


/*program by mangoyang*/
#include<bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
    int ch = 0, f = 0; x = 0;
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
    for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
    if(f) x = -x;
}

#define mk make_pair
#define fi first
#define se second

const int N = 500005;
char s[N];
int pos[N], Ans[N], m;

struct Node{ int id, l, r; }; vector<Node> q[N];

struct SegmentTree{
    int val[N*25], lc[N*25], rc[N*25], rt[N*10], size;
    inline SegmentTree(){ memset(val, 127, sizeof(val)); }
    inline void ins(int &u, int l, int r, int pos, int x){
        if(!u) u = ++size;
        if(l == r) return (void) (val[u] = min(val[u], x));
        int mid = l + r >> 1;
        if(pos <= mid) ins(lc[u], l, mid, pos, x);
        else ins(rc[u], mid + 1, r, pos, x);
        val[u] = min(val[lc[u]], val[rc[u]]);
    }
    inline int query(int u, int l, int r, int pos, int lim){
        if(!u) return 0;
        if(l == r) return val[u] < lim ? l : 0;
        int mid = l + r >> 1, res = 0;
        if(mid + 1 >= pos) return query(lc[u], l, mid, pos, lim);       
        if(val[rc[u]] < lim) res = query(rc[u], mid + 1, r, pos, lim);
        if(!res) return query(lc[u], l, mid, pos, lim); else return res;
    }
    inline int merge(int x, int y, int l, int r){
        if(!x || !y) return x + y;
        int o = ++size, mid = l + r >> 1;
        if(l == r) val[o] = min(val[x], val[y]);
        else{
            lc[o] = merge(lc[x], lc[y], l, mid);
            rc[o] = merge(rc[x], rc[y], mid + 1, r);
            val[o] = min(val[lc[o]], val[rc[o]]);
        }
        return o;
    }
}T1, T2;

namespace Sam{
    vector<int> g[N];
    vector< pair<int, int> > s[N];
    int sz[N], top[N], ms[N];
    int ch[N][26], len[N], fa[N], id[N], size = 1, tail = 1;

    inline int newnode(int x){ return len[++size] = x, size; }
    inline void ins(int c, int x){
        int p = tail, np = newnode(len[p] + 1); 
        pos[x] = np, id[np] = x; 
        for(; p && !ch[p][c]; p = fa[p]) ch[p][c] = np;
        if(!p) return (void) (fa[np] = 1, tail = np);
        int q = ch[p][c];
        if(len[q] == len[p] + 1) fa[np] = q;
        else{
            int nq = newnode(len[p] + 1);
            fa[nq] = fa[q], fa[q] = fa[np] = nq;
            for(int i = 0; i < 26; i++) ch[nq][i] = ch[q][i];
            for(; p && ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
        }tail = np;
    }
    inline void addedge(){
        for(int i = 2; i <= size; i++) g[fa[i]].push_back(i);
    }
    inline void Buildtree(int u){
        if(id[u]) T1.ins(T1.rt[u], 1, size, id[u], id[u]);
        sz[u] = 1;
        for(int i = 0; i < (int) g[u].size(); i++){
            int v = g[u][i]; Buildtree(v);
            T1.rt[u] = T1.merge(T1.rt[u], T1.rt[v], 1, size);
            sz[u] += sz[v];
            if(sz[v] > sz[ms[u]]) ms[u] = v;
        }
    }
    inline void prework(int u, int x){
        if(id[u]) s[x].push_back(mk(id[u], id[u] - len[x]));
        for(int i = 0; i < (int) g[u].size(); i++) prework(g[u][i], x);
    }
    inline void dfs(int u, int chain){
        top[u] = chain; 
        if(id[u]) s[u].push_back(mk(id[u], id[u] - len[u]));
        if(ms[u]) dfs(ms[u], chain);
        for(int i = 0; i < (int) g[u].size(); i++){
            int v = g[u][i];
            if(v != ms[u]) dfs(v, v), prework(v, u);
        }
    }
    inline void Doit(){ addedge(), Buildtree(1), dfs(1, 1); }
    inline int query(int u, int l, int r, Node Query){
        int res = T1.query(T1.rt[u], 1, size, r, l + len[u]);
        while(u){
            q[u].push_back(Query), u = fa[top[u]];
            if(!u) break;
            res = max(res, T1.query(T1.rt[u], 1, size, r, l + len[u]));
        }
        return res < l ? 0 : res - l + 1;
    }
    inline void solve(int u){
        for(int i = 0; i < (int) s[u].size(); i++)
            T2.ins(T2.rt[top[u]], 1, size, s[u][i].fi, s[u][i].se);
        for(int i = 0; i < (int) q[u].size(); i++){
            int l = q[u][i].l, r = q[u][i].r;
            int now = T2.query(T2.rt[top[u]], 1, size, r, l);
            Ans[q[u][i].id] = max(Ans[q[u][i].id], (now < l ? 0 : now - l + 1));
        }
        for(int i = 0; i < (int) g[u].size(); i++) solve(g[u][i]);
    }
}
int main(){
    scanf("%s", s + 1); int len = strlen(s + 1);
    for(int i = 1; i <= len; i++) Sam::ins(s[i] - 'a', i);
    Sam::Doit(); read(m);
    for(int i = 1, l, r; i <= m; i++){
        read(l), read(r);
        Ans[i] = Sam::query(pos[r], l, r, (Node){i, l, r});
    }
    Sam::solve(1);
    for(int i = 1; i <= m; i++) printf("%d\n", Ans[i]);
    return 0;
}

转载于:https://www.cnblogs.com/mangoyang/p/9794736.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值