hdu 6704 后缀数组+主席树+线段树

hdu 6704 后缀数组+主席树+线段树

题目大意

给一个字符串以及若干询问;询问中描述了一个子串,以及一个数字x,问子串出现的第x次是在原串的哪个位置。题目在此

思路

类似询问某个子串出现次数的题目,能够想到用后缀数组来解。后缀数组是将原串的所有后缀按照字典序排序,那么如果某个子串出现了n次,那么分别将这n个子串作为前缀的后缀串必定在后缀数组中连续出现。我们只需要找到这个区间,对这个区间求后缀起始位置的第K小即可,此处可使用主席树。寻找这个区间的方法有很多种。本人的队友想到了线段树查找,当然也可以使用ST表+二分来查找区间。AC代码是后缀数组+主席树+线段树的策略。

AC代码

#include <bits/stdc++.h>

using namespace std;

const int CHAR_NUM = 128;

const int MAXN = 2e5 + 10;
typedef long long ll;
#define per(i, a, n) for (int i=a;i<n;i++)
#define w(i) T[(i)].w
#define ls(i) T[(i)].ls
#define rs(i) T[(i)].rs

struct node {
    int ls, rs;                  //左儿子,右儿子
    ll w;                       //下面点数
    node() { ls = rs = w = 0; }
} T[MAXN * 20];

ll b[MAXN], p[MAXN];     //原始数据,离散化后点值,离散化过程数组
int root[MAXN], sz;      //第i棵树根节点,总点数
//插入
void ins(int &i, int l, int r, int x) {
    T[++sz] = T[i];
    i = sz;
    w(i)++;
    if (l == r) return;
    int m = (l + r) >> 1;
    if (x <= m) ins(ls(i), l, m, x);
    else ins(rs(i), m + 1, r, x);
}

//查询区间[l, r] 第k小(第1小表示最小)
//query(root[l-1], root[r], 1, N, k);
int query(int i, int j, int l, int r, int k) {
    if (l == r) return l;
    int t = w(ls(j)) - w(ls(i));
    int m = (l + r) >> 1;
    if (t >= k) return query(ls(i), ls(j), l, m, k);
    else return query(rs(i), rs(j), m + 1, r, k - t);
}

int SA[MAXN], myRank[MAXN], height[MAXN], sum[MAXN], tp[MAXN];
//rank[i] 第i个后缀的排名, SA[i] 排名为i的后缀的位置, Height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP
//sum[i] 基数排序辅助数组, 存储小于i的元素有多少个, tp[i] rank的辅助数组(按第二关键字排序的结果),与SA意义一样

bool cmp(const int *f, int x, int y, int w) {
    return f[x] == f[y] && f[x + w] == f[y + w];
}

void get_SA(const char *s, int n, int m) {
    //先预处理长度为1的情况
    for (int i = 0; i < m; i++) sum[i] = 0;//清0
    for (int i = 0; i < n; i++) sum[myRank[i] = s[i]]++;//统计每个字符出现的次数
    for (int i = 1; i < m; i++) sum[i] += sum[i - 1];//sum[i]为小于等于i的元素的数目
    for (int i = n - 1; i >= 0; i--) SA[--sum[myRank[i]]] = i;//下标从0开始,所以先自减
    //SA[i]存储排名第i的后缀下标,SA[--sum[rank[i]]] = i 即下标为i的后缀排名为--sum[rank[i]],这很显然
    for (int len = 1; len <= n; len *= 2) {
        int p = 0;
        //直接用SA数组对第二关键字排序
        for (int i = n - len; i < n; i++) tp[p++] = i;//后面i个数没有第二关键字,即第二关键字为空,所以最小
        for (int i = 0; i < n; i++) {
            if (SA[i] >= len) tp[p++] = SA[i] - len;
        }
        //tp[i]存储按第二关键字排序第i的下标
        //对第二关键字排序的结果再按第一关键字排序,和长度为1的情况类似
        for (int i = 0; i < m; i++) sum[i] = 0;
        for (int i = 0; i < n; i++) sum[myRank[tp[i]]]++;
        for (int i = 1; i < m; i++) sum[i] += sum[i - 1];
        for (int i = n - 1; i >= 0; i--) SA[--sum[myRank[tp[i]]]] = tp[i];
        //根据SA和rank数组重新计算rank数组
        swap(myRank, tp);//交换后tp指向旧的rank数组
        p = 1;
        myRank[SA[0]] = 0;
        for (int i = 1; i < n; i++) {
            myRank[SA[i]] = cmp(tp, SA[i - 1], SA[i], len) ? p - 1 : p++;//注意判定rank[i]和rank[i-1]是否相等
        }
        if (p >= n) break;
        m = p;//下次基数排序的最大值
    }
    //求height
    int k = 0;
    n--;
    for (int i = 0; i <= n; i++) myRank[SA[i]] = i;
    for (int i = 0; i < n; i++) {
        if (k) k--;
        int j = SA[myRank[i] - 1];
        while (s[i + k] == s[j + k]) k++;
        height[myRank[i]] = k;
    }
}

/**
 * _ooOoo_
 * o8888888o
 * 88" . "88
 * (| -_- |)
 *  O\ = /O
 * ___/`---'\____
 * .   ' \\| |// `.
 * / \\||| : |||// \
 * / _||||| -:- |||||- \
 * | | \\\ - /// | |
 * | \_| ''\---/'' | |
 * \ .-\__ `-` ___/-. /
 * ___`. .' /--.--\ `. . __
 * ."" '< `.___\_<|>_/___.' >'"".
 * | | : `- \`.;`\ _ /`;.`/ - ` : | |
 * \ \ `-. \_ __\ /__ _/ .-` / /
 * ======`-.____`-.___\_____/___.-`____.-'======
 * `=---='
 *          .............................................
 *           佛曰:bug泛滥,我已瘫痪!
 */

char str[MAXN];

/*
 * 主席树求区间第 k 小,离线查询
 * 先将变量保存至数组 a 中,点编号为 1 - n
 * 将变量 tot 初始化为 0
 * 然后调用 Init_hash 函数完成建树
 * 之后便可以进行询问操作,返回第 k 小的值
 * 重复数字会重复结算
 */
const int M = MAXN * 30;        // 建树的数组大小,至少 30 倍
int n;

struct SegTree {
    int mnn[MAXN << 2];

    inline void PushUp(int rt) {
        mnn[rt] = min(mnn[rt << 1], mnn[rt << 1 | 1]);
    }

    inline void Build(int l, int r, int rt) {
        if (l == r) {
            mnn[rt] = height[l];
            return;
        }
        int mid = (l + r) >> 1;
        Build(l, mid, rt << 1);
        Build(mid + 1, r, rt << 1 | 1);
        PushUp(rt);
    }

    inline int RQuery(int L, int R, int C, int l, int r, int rt) {   //查找区间[L, R]内比C小的第一个
        if (L <= l && r <= R) {
            if (l == r) return mnn[rt] >= C ? n + 1 : l;
            int mid = (l + r) >> 1;
            if (mnn[rt << 1] >= C)
                if (mnn[rt << 1 | 1] >= C) return n + 1;
                else return RQuery(L, R, C, mid + 1, r, rt << 1 | 1);
            return RQuery(L, R, C, l, mid, rt << 1);
        }
        int mid = (l + r) >> 1;
        if (L > mid) { return RQuery(L, R, C, mid + 1, r, rt << 1 | 1); }
        else { return min(RQuery(L, R, C, l, mid, rt << 1), RQuery(L, R, C, mid + 1, r, rt << 1 | 1)); }
    }

    inline int LQuery(int L, int R, int C, int l, int r, int rt) {   //查找区间[L, R]内比C小的最后一个
        if (L <= l && r <= R) {
            if (l == r) return mnn[rt] >= C ? 0 : l;
            int mid = (l + r) >> 1;
            if (mnn[rt << 1 | 1] >= C)
                if (mnn[rt << 1] >= C) return 0;
                else return LQuery(L, R, C, l, mid, rt << 1);
            return LQuery(L, R, C, mid + 1, r, rt << 1 | 1);
        }
        int mid = (l + r) >> 1;
        if (R <= mid) { return LQuery(L, R, C, l, mid, rt << 1); }
        else { return max(LQuery(L, R, C, l, mid, rt << 1), LQuery(L, R, C, mid + 1, r, rt << 1 | 1)); }
    }
} XyhST;

int tmpl, tmpr, CC;
int index;

void xuyuehao() {
    tmpl = 1, tmpr = n + 1;
    if (index != 1)
        tmpl = XyhST.LQuery(1, index, CC, 1, n, 1);
    if (index != n)
        tmpr = XyhST.RQuery(index + 1, n, CC, 1, n, 1);
    tmpr -= 1;
    if(tmpr < tmpl)
        tmpr = tmpl;
}

void reset(int r) {
    for (int i = 0; i < r + 3; i++) {
        SA[i] = myRank[i] = height[i] = sum[i] = tp[i] = 0;
    }
}

int main() {
#ifdef ACM_LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    auto start_____ = clock();
#endif
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int tt;
    cin >> tt;
    while (tt--) {
        int len, qc;
        cin >> len >> qc;
        cin >> str;

        str[len] = 0;
        len++;
        n = len;
        get_SA(str, len, CHAR_NUM);
        n--;
        per(i, 1, n + 1) { p[i] = i; }
        sort(p + 1, p + 1 + n, [](int i, int j) { return SA[i] < SA[j]; });
        per(i, 1, n + 1) { b[p[i]] = i; }

        root[0] = 0;
        sz = 0;
        per(i, 1, n + 1) {
            root[i] = root[i - 1];
            ins(root[i], 1, n, b[i]);
        }


        len--;
        int l, r, k;
        XyhST.Build(1, n, 1);
        while (qc--) {
            cin >> l >> r >> k;
            l--;
            r--;
            //名次
            index = myRank[l];
            if (len - SA[index] < r - l + 1)
                cout << "-1" << endl;
            else {
                CC = r - l + 1;
                xuyuehao();
                if (tmpr - tmpl + 1 < k)
                    cout << "-1" << endl;
                else
                    cout << query(root[tmpl - 1], root[tmpr], 1, n, k) << endl;
            }
        }
        reset(n + 1);
    }

#ifdef ACM_LOCAL
    //    debug(n);
#endif

#ifdef ACM_LOCAL
    auto end_clock_for_debug = clock();
    cerr << "Run Time: " << double(end_clock_for_debug - start_____) / CLOCKS_PER_SEC << "s" << endl;
#endif
    return 0;
}

总结

比赛的时候灵光乍现,想到用后缀数组来解,跟两个队友debug了两个小时(因为用的板子太多了,简直用尽毕生所学),最后一发入魂简直爽呆。但是感觉自己的数据结构基础还是不够硬,比赛的时候主席树的板子都不会用了,嘤嘤嘤~,回头找找数据结构的题目再多练练。听说题解是后缀自动姬fail树上dfs序建主席树做的,听上去感觉比较复杂,回头会去尝试一下这种解法,但我还是觉得后缀数组的解法更加简单,嘻嘻嘻。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值