@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 小子串问题。