cf 700 - E Cool Slogans

Description

定义字符串\(A\)\(B\)酷 : \(B\)至少在\(A\)中出现\(2\)

给你一个长度\(\le 2*10^5\)的字符串s

求在其中选择若干子串(随便选,可重叠), 排成一列, 且任意后一个子串比前一个子串\(Cool\)的方案中

最长的长度是多少

Analysis

出现两次...选了一个串要先收缩然后找到合法前缀然后再收缩什么... 没有思路啊

我们反过来从答案入手, 对于任意一个最优解, 进行下图的收缩

1086046-20170918200537603-1956272426.jpg

即: 从下到上进行如下操作. 选择每组中最左的一个, 做一条切割线, 保留上一层切割线右的部分

这样, 每一层的字符串都是上一层的前缀

根据贪心, 我们一定选择由上一层选择一个最长的出现至少2次的前缀

Solution

考虑后缀树结构

一个子串的前缀为其在后缀树上的祖先

我们需要实现 : (1)判断原串的某个前缀是否在原串出现至少两次 (2) 求出最长的一个

判断的话, 我们可以考虑\(left\)

因为一个串出现在什么地方是没有影响的因为本质相同

因为前缀在开头出现了一次, 再出现一次就好了

所以我们可以求出原串的最左出现位置\(p\), 然后求出该前缀\(>p\)的最左出现位置

判断长度是否合法即可

因为要求出最长的合法, 而\(fa\)会导致串变短

而合法与不合法有分界线, 可以二分

所以我们树上倍增 跳出 的不合法点的极长距离, 然后再往上走一步就好了

Code

#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <algorithm>
#define rep(i,a,b) for (int i = (a); i <= (b); ++ i)
#define per(i,a,b) for (int i = (a); i >= (b); -- i)
#define For(i,a,b) for (int i = (a); i < (b); ++ i)
using namespace std;
const int M = 2e5 + 7;
const int INF = 1e9 + 7;

int n;
char s[M];

namespace Seg{
    static const int N = (M << 1) * 20 * 2;
    int n, rt[N], tot;
    struct node{
        int lc, rc;
    }a[N];

    inline int newnode(int x){
        a[++tot] = a[x];
        return tot;
    }

    void ins(int &x, int l, int r, int to){
        x = newnode(0);
        if (l == r) return;
        int mid = l+r >> 1;
        if (to <= mid) ins(a[x].lc, l, mid, to);
        else ins(a[x].rc, mid+1, r, to);
    }

    int merge(int x, int y, int l, int r){
        if (!x || !y) return x | y;
        if (l == r) return newnode(x);
        int mid = l+r >> 1;
        int nw = newnode(x);
        a[nw].lc = merge(a[x].lc, a[y].lc, l, mid);
        a[nw].rc = merge(a[x].rc, a[y].rc, mid+1, r);
        return nw;
    }

    int nxt(int x, int l, int r, int p){
        if (!x) return INF;
        if (l == r) return (p != l) ? l : INF;
        int mid = l+r >> 1;
        if (p > mid) return nxt(a[x].rc, mid+1, r, p);
        else{
            int tp = nxt(a[x].lc, l, mid, p);
            return (tp != INF) ? tp : nxt(a[x].rc, mid+1, r, p);
        }
    }
    
    int nxt(int x, int p) {return nxt(rt[x], 1, n, p);}
    void ins(int x, int to) {ins(rt[x], 1, n, to);}
    void merge(int x, int y) {rt[x] = merge(rt[x], rt[y], 1, n);}
}

namespace ST{
    static const int N = M << 1;
    int n, D, pre[N][20], mx[N], stp[N], lf[N];
    struct vec{
        int g[N], te;
        struct edge{int y, nxt;}e[N];
        inline void push(int x, int y) {e[++te] = (edge){y, g[x]}; g[x] = te;}
        inline int& operator () (int x) {return g[x];}
        inline edge& operator [] (int x) {return e[x];}
    }e;

    void init(int x){
        if (lf[x]) Seg::ins(x, lf[x]);
        int p, y;
        for (p=e(x); p; p=e[p].nxt)
        if ((y=e[p].y) != pre[x][0]){
            init(y);
            Seg::merge(x, y);
        }
    }

    void build(){
        D = (int)log2(n);
        rep (j, 1, D)
        rep (i, 1, n) pre[i][j] = pre[pre[i][j-1]][j-1];
        init(1);
    }

    int getmx(int x){
        int p = Seg::nxt(x, 0);
        int r = p + stp[x] - 1;
        int y;
        per (t, D, 0) if (y = pre[x][t]) {
            int l = Seg::nxt(y, p) + stp[y] - 1;
            if (l > r) x = y;   
        }
        return mx[pre[x][0]];
    }
    
    void dfs(int x){
        int p, y;

        mx[x] = (x == 1) ? 0 : 1 + getmx(x);

        for (p=e(x); p; p=e[p].nxt)
        if ((y=e[p].y) != pre[x][0]) dfs(y);
    }

    int solve(){
        dfs(1);
        
        int res = 0;
        rep (i, 1, n) res = max(res, mx[i]);
        return res;
    }

    inline void link(int x, int y) {e.push(x, y); pre[y][0] = x;}
}

namespace Sam{
    static const int N = M << 1;
    int fa[N], stp[N], ch[N][26], lf[N];
    int last, tot;

    inline int newnode(int l){
        stp[++tot] = l;
        return tot;
    }

    int ext(int p, int q, int c){
        int nq = newnode(stp[p] + 1);
        fa[nq] = fa[q], fa[q] = nq;
        memcpy(ch[nq], ch[q], sizeof ch[q]);
        for (; p && ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
        return nq;
    }

    int sam(int p, int c){
        int np = newnode(stp[p] + 1);
        for (; p && ch[p][c] == 0; p = fa[p]) ch[p][c] = np;
        if (!p) fa[np] = 1;
        else{
            int q = ch[p][c];
            fa[np] = (stp[p] + 1 == stp[q]) ? q : ext(p, q, c);
        }
        return np;
    }

    void build(){
        last = tot = 1;
        per (i, n, 1) lf[last = sam(last, s[i]-'a')] = i;
    }

    void toST(){
        ST::n = Seg::n = tot;
        rep (i, 1, tot) ST::stp[i] = stp[i], ST::lf[i] = lf[i];
        rep (i, 2, tot) ST::link(fa[i], i);
    }
}

int main(){
    
    scanf("%d%s", &n, s+1);

    Sam::build();
    Sam::toST();
    ST::build();
    int ans = ST::solve();
    
    printf("%d\n", ans);

    return 0;
}

转载于:https://www.cnblogs.com/acha/p/7544942.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值