hdu 6704 K-th occurrence(后缀数组+st+主席树)

题意:给你一个字符串,然后有1e5的询问,每次给你一个子串的起始位置和结束位置,询问该子串的第K次出现位置

 

思路:首先询问许多子串的这种问题应该联想到后缀数组,然后我们发现对于后缀数组中的排序(sa[i]表示下标为i的后缀的排名,rk[i]表示排名为i的后缀的下标),每个后缀串与子串的最长公共前缀为该子串的串是连续的,所以我们只要求出这段连续排名的区间(因为在这里的对于子串的最长公共前缀具有单调性,用st表预处理出每个区间的最长公共前缀长度,用二分check即可),然后在这段区间上询问第k大sa[i]的数即可。

 

附上代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inv inline void
#define ini inline int
#define maxn 100050
using namespace std;
int st[20][maxn];
int ndtot, N;
struct segtree { int l, r, size; segtree *ch[2]; }pool[maxn * 30], *root[maxn];
void ins(segtree *pre, segtree *now, int pos)
{
    int mid = (pre->l + pre->r) >> 1;
    *now = *pre; now->size++;
    if (pre->l == pre->r)return;
    if (pos <= mid)ins(pre->ch[0], now->ch[0] = pool + ++ndtot, pos);
    if (pos > mid)ins(pre->ch[1], now->ch[1] = pool + ++ndtot, pos);
}
int find(segtree *pre, segtree *now, int k)
{
    int sz;
    if (pre->l == pre->r)return pre->l;
    sz = now->ch[0]->size - pre->ch[0]->size;
    if (k <= sz)return find(pre->ch[0], now->ch[0], k);
    return find(pre->ch[1], now->ch[1], k - sz);
}
void build(segtree *p, int l, int r)
{
    int mid = (l + r) >> 1;
    p->l = l, p->r = r, p->size = 0;
    if (l == r)return;
    build(p->ch[0] = pool + ++ndtot, l, mid);
    build(p->ch[1] = pool + ++ndtot, mid + 1, r);
}


char s[maxn];
int y[maxn], x[maxn], c[maxn], sa[maxn], rk[maxn], height[maxn];
int n, m;
inv get_SA() {
    memset(c, 0, sizeof(c));
    for (int i = 1; i <= n; ++i) ++c[x[i] = s[i]];
    for (int i = 2; i <= m; ++i) c[i] += c[i - 1];
    for (int i = n; i >= 1; --i) sa[c[x[i]]--] = i;
    for (int k = 1; k <= n; k <<= 1) {
        int num = 0;
        for (int i = n - k + 1; i <= n; ++i) y[++num] = i;
        for (int i = 1; i <= n; ++i) if (sa[i] > k) y[++num] = sa[i] - k;
        for (int i = 1; i <= m; ++i) c[i] = 0;
        for (int i = 1; i <= n; ++i) ++c[x[i]];
        for (int i = 2; i <= m; ++i) c[i] += c[i - 1];
        for (int i = n; i >= 1; --i) sa[c[x[y[i]]]--] = y[i], y[i] = 0;
        swap(x, y);
        x[sa[1]] = 1;
        num = 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]) ? num : ++num;
        if (num == n) break;
        m = num;
    }
}
inv get_height() {
    int k = 0;
    for (int i = 1; i <= n; ++i) rk[sa[i]] = i;
    for (int i = 1; i <= n; ++i) {
        if (rk[i] == 1) continue;
        if (k) --k;
        int j = sa[rk[i] - 1];
        while (j + k <= n && i + k <= n && s[i + k] == s[j + k]) ++k;
        height[rk[i]] = k;
    }
}
void build_st() {
    for (int i = 1; i <= n; i++) st[0][i] = height[i];
    for (int k = 1; k <= 19; k++) {
        for (int i = 1; i + (1 << k) - 1 <= n; i++) {
            st[k][i] = min(st[k - 1][i], st[k - 1][i + (1 << k - 1)]);
        }
    }
}
int lcp(int x, int y) {
    int l = rk[x], r = rk[y];
    if (l > r) swap(l, r);
    if (l == r) return n - x + 1;
    int t = log2(r - l);
    return min(st[t][l + 1], st[t][r - (1 << t) + 1]);
}
int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        int len, q;
        scanf("%d%d", &len, &q);
        scanf("%s", s + 1);
        n = strlen(s + 1);
        m = 123;
        get_SA();
        get_height();
        build_st();
        N = n;
        ndtot = 0;
        build(root[0] = pool + ++ndtot, 1, N);
        for (int i = 1; i <= N; i++)ins(root[i - 1], root[i] = pool + ++ndtot, sa[i]);
        while (q--) {
            int l, r, k;
            scanf("%d%d%d", &l, &r, &k);
            int ll, rr;
            int length = r - l + 1;
            int left = 1, right = rk[l];
            int ans = rk[l];
            while (left <= right) {
                int mid = (left + right) / 2;
                if (lcp(sa[mid], l) >= length) {
                    right = mid - 1;
                    ans = mid;
                }
                else {
                    left = mid + 1;
                }
            }
            ll = ans;
            ans = rk[l];
            left = rk[l], right = n;
            while (left <= right) {
                int mid = (left + right) / 2;
                if (lcp(sa[mid], l) >= length) {
                    left = mid + 1;
                    ans = mid;
                }
                else {
                    right = mid - 1;
                }
            }
            rr = ans;
            if (rr - ll + 1 < k) printf("-1\n");
            else printf("%d\n", find(root[ll - 1], root[rr], k));
        }
    }
}

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值