hdu-6704

题意

首先给了我们一个字符串,姑且命名为文本串,然后有q个查询,对于每个查询,包含两个数l,r 询问在文本串l到r这段子串在文本串中第k次出现的首字母的位置,不满足输出-1

分析

我们首先可以想到用ac自动机,对所有查询建立一个ac自动机,然后把文本串放上面跑。可是现实打击了我们,范围太大,建字典树就超内存了。于是我们又想到后缀数组也可以处理类似问题,然后对于第k大,我们可以用主席树去维护。(当然后缀自动机也行,篛蒻的我不会)
我们可以对文本串建立后缀数组,用sa数组去建立主席树。然后对与每个查询,我们二分出左右区间(即存在查询子串的排名区间),对这个区间找第k小就行

/*后缀数组+主席树*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 1e5 + 100;
struct node//静态主席树节点 
{
    int l, r, val;
}T[N * 50];
int root[N], sa[N], tp[N], rak[N], tax[N], height[N];
/*
sa[i] 表示 排名为i的后缀的首字母位置
tp[i] 第二关键字,性质同sa
rak[i] 表示 第i个后缀的排名
tax[i] 排名为i的数量,桶
height[i] = lcp(sa[i - 1], sa[i]) 排名相邻的两个后缀的最长公共前缀
*/
int dp[N][32], lg2[N];
char s[N];
int n, m, q, cnt;
void Qsort()//基数排序
{
    for(int i = 0; i <= m; i ++)
        tax[i] = 0;
    for(int i = 1; i <= n; i ++)
        tax[rak[i]] ++;
    for(int i = 1; i <= m; i ++)
        tax[i] += tax[i - 1];
    for(int i = n; i >= 1; i --)
        sa[tax[rak[tp[i]]] --] = tp[i];
}
void get_SA()//获取sa
{
    for(int i = 1; i <= n; i ++)
    {
        rak[i] = (int)s[i];
        tp[i] = i;
    }
    Qsort();
    for(int w = 1, p = 0; p < n; m = p, w <<= 1)
    {
        p = 0;
        for(int i = 1; i <= w; i ++)
            tp[++ p] = n - w + i;
        for(int i = 1; i <= n; i ++)
            if(sa[i] > w)
                tp[++ p] = sa[i] - w;
        Qsort();
        swap(rak, tp);
        rak[sa[1]] = p = 1;
        for(int i = 2; i <= n; i ++)
            rak[sa[i]] = (tp[sa[i]] == tp[sa[i - 1]] && tp[sa[i] + w] == tp[sa[i - 1] + w]) ? p : ++ p;
    }
}
/*
h[i] = height[rak[i]]
h[i] >= h[i - 1] - 1
*/
void get_HEIGHT()//获取height
{
    int k = 0;
    for(int i = 1; i <= n; i ++)
    {
        if(k)   
            k --;
        int j = sa[rak[i] - 1];
        while(s[i + k] == s[j + k])
            k ++;
        height[rak[i]] = k;
    }
}
void init()//建立st表,求区间最值
{
    for(int i = 1; i <= n; i ++)
        dp[i][0] = height[i];
    for(int j = 1; (1 << j) <= n; j ++)
        for(int i = 1; i + (1 << j) - 1 <= n; i ++)
            dp[i][j] = min(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
int rmq(int l, int r)//查询最值
{
    if(l == r)
        return n;
    if(l > r)
        swap(l, r);
    l ++;//lcp(x, y) = min(lcp(k - 1, k)) (x < k <= y)
    int k = lg2[r - l + 1];
    return min(dp[l][k], dp[r - (1 << k) + 1][k]);
}
void update(int l, int r, int &x, int y, int pos)//主席树插入点
{
    T[++ cnt] = T[y];//将原节点赋值给新节点
    T[cnt].val ++;//计数加一
    x = cnt;
    if(l == r)
        return ;
    int mid = (l + r) >> 1;
    if(mid >= pos)
        update(l, mid, T[x].l, T[y].l, pos);
    else 
        update(mid + 1, r, T[x].r, T[y].r, pos);
}
int query(int l, int r, int x, int y, int k)//查询区间第k小
{
    if(l == r)
        return l;
    int mid = (l + r) >> 1;
    int tmp = T[T[y].l].val - T[T[x].l].val;
    if(tmp >= k)//左边的数量超过了k,说明结果在左子树上
        return query(l, mid, T[x].l, T[y].l, k);
    else //反之在右子树上
        return query(mid + 1, r, T[x].r, T[y].r, k - tmp);
}
int main()
{
    ios::sync_with_stdio(false);
    int t;
    cin >> t;
    for(int i = 2; i <= N; i ++)//常数优化
        lg2[i] = lg2[i / 2] + 1;
    while(t --)
    {
        cnt = 0;
        memset(root, 0, sizeof(root));
        memset(T, 0, sizeof(T));
        memset(dp, 0x3f, sizeof(dp));
        cin >> n >> q;
        cin >> (s + 1);
        m = (int)'z';
        get_SA();
        get_HEIGHT();
        init();
        for(int i = 1; i <= n; i ++)
            update(1, n, root[i], root[i - 1], sa[i]);
        while(q --)
        {
            int l, r, k;
            cin >> l >> r >> k;
            int len = r - l + 1;
            int left = 1, right = rak[l];
            int ans = rak[l];
            while(left <= right)//二分到左边界
            {
                int mid = (left + right) >> 1;
                if(rmq(mid, rak[l]) >= len)
                {
                    ans = mid;
                    right = mid - 1;
                }
                else 
                    left = mid + 1;
            }
            int el = ans;
            ans = rak[l];
            left = rak[l], right = n;
            while(left <= right)//二分到右边界
            {
                int mid = (left + right) >> 1;
                if(rmq(mid, rak[l]) >= len)
                {
                    ans = mid;
                    left = mid + 1;
                }
                else 
                    right = mid - 1;
            }
            int er = ans;
            if(er - el + 1 < k) //区间内排名数量不足k个
                cout << -1 << endl;
            else 
                cout << query(1, n, root[el - 1], root[er], k) << endl;
        }
    }
}