NOI2015模拟SXK 字符串游戏 后缀数组预处理+主席树查询

题目大意

给你一个长度为 N 的字符串T T[l,r] 表示 T 中第l个字符到第 r 个字符组成的子串,现在给你两种询问s’j’d’f’ha’s’k’l’j’d’f
1. 给你两个整数k1 k2 ,询问在所有 T 不相等的字符串中,字典序从小到大排序,排在第k1位的字符串 T[l,r] ,如果该子串出现了多次,则询问起始位置第 k2 小的那个。输出询问的那个子串的起始位置和终止位置(即 l r
2. 给定两个整数 l,r ,询问子串 T[l,r] T 中不相同子串字典序排名k1,以及在相同子串中从小到大的起始位置排名 k2
现在有 M 组询问,每组询问有三个正整数type,x,y,如果 type=1 则为第一种询问,否则为第二种询问,保证数据合法。

N500000 M100000

解题思路

首先我们考虑第一种询问。找出排名为 k1 的子串是 SA 的基本功能,这里就简要介绍一下。因为求的是不相同子串的字典序,所以按后缀排名后 LenSAi+1Heighti 就是每个后缀增加的子串个数,且字典序递增。我们可以用前缀和 Sumi 表示到拍完名的第 i 个后缀总共产生的字符串,我们只需二分一下就可以找出排名为第k1的字符串。
然后我们要考虑的就是相同子串中起始位置排名 k2 小的子串。由于拍完序后表示一段相同的子串的后缀肯定是连续一段,所以我们可以用二分确定能表示这个子串的区间。然后我们要做的就是在这个区间查询起始位置第 k2 的子串。这个是不是很眼熟,这就是区间第 k 小问题,只需用个主席树就可以解决了。到此第一种情况就解决了。

那么到了第二种情况,其实也差不多,只不过变成了给我们一个子串T[l,r],设其长度为 len=rl+1 。我们可以从 Rankl 中得知这个后缀在排序后的 SA 中的位置。根第一种询问相同,我们同样也可以先确定出能表示出这个子串的区间,假设这个区间的第一个数在 SA 中的位置是 p ,那么Sump1+len+Height[p]就是我们要求的 k1 。为什么呢,显然的是 Heightp 的值必定是比 len 要小的,不然 p1 就也应该在我们寻找的区间中,就矛盾了,得知这个我们就可以根据上面每个后缀增加子串的公式得到排名。同样 k2 为在这段区间中起始位置比 Rankl 前的相同子串用多少个,也用主席树就可以得到答案,那么这个第二种询问也解决了。

程序

由于把主席树和后缀数组强行搞在了一起,所以有点小长。

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
typedef long long LL;

const int MAXN = 5e5 + 5;

struct Tree {int l, r, Cnt;} Tr[MAXN * 20];

int Len, M, SA[MAXN], rank[MAXN], Height[MAXN], tax[MAXN], tp[MAXN];
int tot, N, Log[MAXN], Root[MAXN], Rmq[MAXN][21];
LL Sum[MAXN];
char S[MAXN];

void RSort() {
    for (int i = 0; i <= M; i ++) tax[i] = 0;
    for (int i = 1; i <= Len; i ++) tax[rank[tp[i]]] ++;
    for (int i = 1; i <= M; i ++) tax[i] += tax[i - 1];
    for (int i = Len; i >= 1; i --) SA[tax[rank[tp[i]]] --] = tp[i]; 
}

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

void Suffix() {
    Len = strlen(S + 1);
    for (int i = 1; i <= Len; i ++) rank[i] = S[i], tp[i] = i;
    M = 127, RSort();

    for (int i, p = 1, w = 1; p < Len; w += w, M = p) {
        for (p = 0, i = Len - w + 1; i <= Len; i ++) tp[++ p] = i;
        for (i = 1; i <= Len; i ++) if (SA[i] > w) tp[++ p] = SA[i] - w;
        RSort(), swap(rank, tp), p = rank[SA[1]] = 1;
        for (i = 2; i <= Len; i ++) rank[SA[i]] = cmp(tp, SA[i], SA[i - 1], w) ? p : ++ p;
    }

    int j, k = 0;
    for (int i = 1; i <= Len; Height[rank[i ++]] = k)
        for (k = k ? k - 1 : k, j = SA[rank[i] - 1]; S[i + k] == S[j + k]; k ++);
}

void Insert(int &Now, int l, int r, int Rt, int Side) {
    Now = ++ tot;
    Tr[Now] = Tr[Rt];
    Tr[Now].Cnt ++;
    if (l == r) return;
    int Mid = (l + r) >> 1;
    if (Side <= Mid) Insert(Tr[Now].l, l, Mid, Tr[Rt].l, Side); else
        Insert(Tr[Now].r, Mid + 1, r, Tr[Rt].r, Side);
}

int Query(int l, int r, int Rank, int Rl, int Rr) {
    if (l == r) return l;
    int Mid = (l + r) >> 1;
    int ll = Tr[Rl].l, lr = Tr[Rl].r, rl = Tr[Rr].l, rr = Tr[Rr].r;
    if (Tr[rl].Cnt - Tr[ll].Cnt >= Rank) return Query(l, Mid, Rank, ll, rl); else
        return Query(Mid + 1, r, Rank - Tr[rl].Cnt + Tr[ll].Cnt, lr, rr); 
}

int GetNum(int l, int r, int lx, int rx, int Rl, int Rr) {
    if (rx < l || lx > r) return 0;
    if (lx <= l && rx >= r) return Tr[Rr].Cnt - Tr[Rl].Cnt;
    int Mid = (l + r) >> 1;
    return GetNum(l, Mid, lx, rx, Tr[Rl].l, Tr[Rr].l) + GetNum(Mid + 1, r, lx, rx, Tr[Rl].r, Tr[Rr].r);
}

int Min(int l, int r) {
    int Len = Log[r - l + 1];
    return min(Rmq[l][Len], Rmq[r - (1 << Len) + 1][Len]);
}

void Prepare() {
    for (int i = 1; i <= Len; i ++) Insert(Root[i], 1, Len, Root[i - 1], SA[i]);

    for (int i = 1, j = 0; i <= Len; i <<= 1, j ++) Log[i] = j;
    for (int i = 2; i <= Len; i++) Log[i] = max(Log[i - 1], Log[i]);

    for (int i = 1; i <= Len; i ++) Rmq[i][0] = Height[i];
    for (int j = 1; j <= 20; j ++)
        for (int i = 1; i <= Len - (1 << (j - 1)) + 1; i ++)
            Rmq[i][j] = min(Rmq[i][j - 1], Rmq[i + (1 << (j - 1))][j - 1]);

    for (int i = 1; i <= Len; i ++) Sum[i] = Sum[i - 1] + Len - SA[i] + 1 - Height[i];
}

int GetL(int Now, int len) {
    int l = 1, r = Now - 1, Ans = Now;
    while (l <= r) {
        int Mid = (l + r) >> 1;
        if (Min(Mid + 1, Now) >= len) Ans = Mid, r = Mid - 1; else l = Mid + 1;
    }
    return Ans;
}

int GetR(int Now, int len) {
    int l = Now + 1, r = Len, Ans = Now;
    while (l <= r) {
        int Mid = (l + r) >> 1;
        if (Min(Now + 1, Mid) >= len) Ans = Mid, l = Mid + 1; else r = Mid - 1;
    }
    return Ans;
}

void Solve1(LL k1, LL k2) {
    int l = 1, r = Len, Ans = 0;
    while (l <= r) {
        int Mid = (l + r) >> 1;
        if (Sum[Mid] < k1) Ans = Mid, l = Mid + 1; else r = Mid - 1; 
    } 
    int len = k1 - Sum[Ans] + Height[++ Ans];
    l = GetL(Ans, len), r = GetR(Ans, len);
    if (l == r) {
        printf("%d %d\n", SA[Ans], SA[Ans] + len - 1);
        return;
    }
    Ans = Query(1, Len, k2, Root[l - 1], Root[r]);
    printf("%d %d\n", Ans, Ans + len - 1);
}

void Solve2(LL l, LL r) {
    int len = r - l + 1, start = l - 1, Rank = rank[l];
    r = GetR(Rank, len), l = GetL(Rank, len);
    LL k1 = Sum[l - 1] + len - Height[l];
    printf("%lld %d\n", k1, GetNum(1, Len, 1, start, Root[l - 1], Root[r]) + 1);
}

int main() {
    freopen("4150.in", "r", stdin), freopen("4150.out", "w", stdout);

    scanf("%s", S + 1);
    Suffix();
    Prepare();
    scanf("%d", &N);
    for (int i = 1; i <= N; i ++) {
        int Ord; LL l, r; 
        scanf("%d%lld%lld", &Ord, &l, &r);
        if (Ord == 1) Solve1(l, r); else Solve2(l, r);
    }
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值