【BZOJ2865】字符串识别

【题目链接】

【前置技能】

  • 线段树
  • 后缀数组

【题解】

  • 先考虑另一个问题:给出一个字符串 S S ,要求求出某一位后缀的最短前缀,使得这个子串在S中仅出现一次。这个问题用后缀数组就可以轻松解决,答案就是 max(height[rank[i]],height[rank[i]1])+1 m a x ( h e i g h t [ r a n k [ i ] ] , h e i g h t [ r a n k [ i ] − 1 ] ) + 1 ,也就是这个后缀最长的在该字符串中出现不止一次的前缀的长度 +1 + 1
  • 那么对于现在正在处理的后缀 pos p o s ,求出它仅出现一次的最短前缀的长度为 cur c u r 。如果 pos+cur1n p o s + c u r − 1 ≤ n ,对于在 [pos,pos+cur1] [ p o s , p o s + c u r − 1 ] 的位置,答案与 cur c u r 取min;如果 pos+cur1<n p o s + c u r − 1 < n ,对于 [pos+cur,n] [ p o s + c u r , n ] 的位置 i i ,答案与ipos+1取min。
  • 用线段树维护一下区间取min,只要每次打上tag最后一起push_down即可。要开两棵线段树,一棵直接维护与 cur c u r 取min,另一棵维护与 1pos 1 − p o s 取min,最后统计的时候加上当前位置的标号 i i 即可。
  • 时间复杂度O(NlogN)

【代码】

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LL  long long
#define MAXN    500010
using namespace std;
int n, sa[MAXN], rnk[MAXN], hei[MAXN];
char s[MAXN];

template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
    x = 0; int f = 1; char ch = getchar();
    while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
    while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
    x *= f;
}

void suffix(int n){
    static int x[MAXN], y[MAXN], rk[MAXN], cnt[MAXN];
    memset(cnt, 0, sizeof(cnt));
    for (int i = 1; i <= n; ++i)
        ++cnt[s[i] - 'a'];
    for (int i = 1; i < 26; ++i)
        cnt[i] += cnt[i - 1];
    for (int i = n; i >= 1; --i)
        sa[cnt[s[i] - 'a']--] = i;
    rnk[sa[1]] = 1;
    for (int i = 2; i <= n; ++i)
        rnk[sa[i]] = rnk[sa[i - 1]] + (s[sa[i]] != s[sa[i - 1]]);
    for (int len = 1; rnk[sa[n]] != n; len <<= 1){
        for (int i = 1; i <= n; ++i)
            x[i] = rnk[i], y[i] = (i + len <= n) ? rnk[i + len] : 0;
        memset(cnt, 0, sizeof(cnt));
        for (int i = 1; i <= n; ++i)
            ++cnt[y[i]];
        for (int i = 1; i <= n; ++i)
            cnt[i] += cnt[i - 1];
        for (int i = n; i >= 1; --i)
            rk[cnt[y[i]]--] = i;
        memset(cnt, 0, sizeof(cnt));
        for (int i = 1; i <= n; ++i)
            ++cnt[x[i]];
        for (int i = 1; i <= n; ++i)
            cnt[i] += cnt[i - 1];
        for (int i = n; i >= 1; --i)
            sa[cnt[x[rk[i]]]--] = rk[i];
        rnk[sa[1]] = 1;
        for (int i = 2; i <= n; ++i)
            rnk[sa[i]] = rnk[sa[i - 1]] + (x[sa[i]] != x[sa[i - 1]] || y[sa[i]] != y[sa[i - 1]]);
    }
} 

void gethei(int n){
    int cur = 0;
    for (int i = 1; i <= n; ++i){
        if (cur) --cur;
        for (int j = sa[rnk[i] + 1]; s[i + cur] == s[j + cur]; ++cur) ;
        hei[rnk[i]] = cur;
    }
}

struct Segment_Tree{
    struct info{int ls, rs, ans;}a[MAXN * 2];
    int b[MAXN];
    int n, root, cnt;
    void build(int &pos, int l, int r){
        pos = ++cnt; a[pos].ans = n;
        if (l == r) return;
        int mid = (l + r) >> 1;
        build(a[pos].ls, l, mid);
        build(a[pos].rs, mid + 1, r);
    }
    void init(int x){
        n = x, cnt = root = 0;
        build(root, 1, n);
    }
    void push_down(int pos, int l, int r){
        if (l == r) {
            b[l] = a[pos].ans;
            return;
        }
        int mid = (l + r) >> 1;
        chkmin(a[a[pos].ls].ans, a[pos].ans);
        chkmin(a[a[pos].rs].ans, a[pos].ans);
        push_down(a[pos].ls, l, mid);
        push_down(a[pos].rs, mid + 1, r);
    }
    void push_down(){
        push_down(root, 1, n);
    }
    void modify(int pos, int l, int r, int ll, int rr, int d){
        if (l == ll && r == rr){
            chkmin(a[pos].ans, d);
            return;
        }
        int mid = (l + r) >> 1;
        if (rr <= mid) modify(a[pos].ls, l, mid, ll, rr, d);
        else if (ll > mid) modify(a[pos].rs, mid + 1, r, ll, rr, d);
        else {
            modify(a[pos].ls, l, mid, ll, mid, d);
            modify(a[pos].rs, mid + 1, r, mid + 1, rr, d);
        }
    }
    void modify(int l, int r, int d){
        modify(root, 1, n, l, r, d);
    }
}sgt[2];

int main(){
    scanf("%s", s + 1);
    n = strlen(s + 1);
    suffix(n);
    gethei(n);
    sgt[0].init(n);
    sgt[1].init(n);
    for (int i = 1; i <= n; ++i){
        int cur = max(hei[rnk[i]], hei[rnk[i] - 1]) + 1;
        if (i + cur - 1 <= n) sgt[0].modify(i, i + cur - 1, cur);
        if (i + cur - 1 < n) sgt[1].modify(i + cur, n, 1 - i);
    }
    sgt[0].push_down();
    sgt[1].push_down();
    for (int i = 1; i <= n; ++i)
        printf("%d\n", min(sgt[0].b[i], sgt[1].b[i] + i));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值