@spoj - sublex@ Lexicographical Substring Search


@description@

给定一个由小写字母构成的字符串。
多次询问。询问它相异子串中字典序第 K 小的子串。

input
第一行给出字符串 S(长度小于等于 90000)。
第二行给出 Q,表示询问个数。Q <= 500。
接下来 Q 行,每行一个 K,表示询问字典序第 K 小的子串。

output
输出 Q 行,第 i 行输出第 i 个询问的答案。

sample input
aaa
2
2
3
sample output
aa
aaa

@solution@

子串必须要不相同。那就只好用后缀自动机咯。

我们知道,后缀自动机的主体部分 DAWG 是一个有向无环图。
我们还知道,后缀自动机中每一个结点表示若干子串。

因此,我们可以求出从某一结点出发能够表示多少子串,记为 size。
假如我们从初始结点寻找字典序最小的子串,肯定是沿着字典序最小的边走。经过上面的处理后,我们可以判断经过这个结点一定能得到字典序最小,第二小,...,第 size 小的子串。
我们要寻找的第 K 小,就可以判断一下是否经过这个结点,如果是就走这个结点,否则寻找下一条相应的边。

@accepted code@

#include<cstdio>
#include<cstring>
const int MAXN = 90000;
typedef long long ll;
struct node{
    node *ch[26], *fa; int mx;
    node *nxt; ll siz;
}pl[2*MAXN + 5], *bin[MAXN + 5], *tcnt, *root, *lst;
void init() {
    tcnt = root = lst = &pl[0];
}
node *newnode() {
    tcnt++;
    return tcnt;
}
void add_bin(node *x) {
    x->nxt = bin[x->mx];
    bin[x->mx] = x;
}
void clone(node *x, node *y) {
    for(int i=0;i<26;i++)
        x->ch[i] = y->ch[i];
    x->fa = y->fa;
}
void extend(int x) {
    node *cur = newnode(), *p = lst;
    cur->mx = lst->mx + 1; lst = cur;
    add_bin(cur);
    while( p && !p->ch[x] )
        p->ch[x] = cur, p = p->fa;
    if( !p )
        cur->fa = root;
    else {
        node *q = p->ch[x];
        if( p->mx + 1 == q->mx )
            cur->fa = q;
        else {
            node *nq = newnode();
            clone(nq, q); nq->mx = p->mx + 1;
            add_bin(nq);
            cur->fa = q->fa = nq;
            while( p && p->ch[x] == q )
                p->ch[x] = nq, p = p->fa;
        }
    }
}
char s[MAXN + 5];
int main() {
    init(); scanf("%s", s);
    int lens = strlen(s);
    for(int i=0;i<lens;i++)
        extend(s[i] - 'a');
    for(int i=lens;i>=1;i--)
        while( bin[i] ) {
            bin[i]->siz = 1;
            for(int j=0;j<26;j++)
                if( bin[i]->ch[j] ) bin[i]->siz += bin[i]->ch[j]->siz;
            bin[i] = bin[i]->nxt;
        }
    int Q; scanf("%d", &Q);
    for(int i=1;i<=Q;i++) {
        int K; scanf("%d", &K);
        node *nw = root;
        while( K ) {
            for(int i=0;i<26;i++)
                if( nw->ch[i] ) {
                    if( K > nw->ch[i]->siz )
                        K -= nw->ch[i]->siz;
                    else {
                        printf("%c", i + 'a');
                        nw = nw->ch[i]; K--;
                        break;
                    }
                }
        }
        puts("");
    }
}

@details@

事实上,如果你处理出每个子串的出现次数(这也是后缀自动机常做的事情),也可以根据这个思路求解可以重复的第 K 小子串问题。

转载于:https://www.cnblogs.com/Tiw-Air-OAO/p/10254769.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值