3998: [TJOI2015]弦论
Time Limit: 10 Sec Memory Limit: 256 MB
Description
对于一个给定长度为N的字符串,求它的第K小子串是什么。
Input
第一行是一个仅由小写英文字母构成的字符串S
第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个。T=1则表示不同位置的相同子串算作多个。K的意义如题所述。
Output
输出仅一行,为一个数字串,为第K小的子串。如果子串数目不足K个,则输出-1
Sample Input
aabc
0 3
Sample Output
aab
HINT
N<=5*10^5
T<2
K<=10^9
思路:
建出后缀自动机求K大的问题,先拓扑排序,然后递推出每个节点能转移到达的子串个数。(能够转移到达的子串都比它大)
这个题需要讨论一下,T=0时说明每个状态代表一个子串,T=1时每个节点的Parent树的子树中的节点数都是可以得到的子串数,所以需要累加。
考虑针对一个点u,它的26个子节点(如果有的话)每个点及其子树(它和所有它能转移到的子串)在子串的大小排列中都是一段连续的区间(排名是连续的),因为所有当前位为b的子串一定比所有当前位为a的子串大,不论后面接什么。
因此只要我们递推的维护了每个节点子树的大小(能转移到的子串个数,包括自己),就可以找到排名第k的子串了。所以可以在后缀自动机上递推,统计出所有子串的贡献。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 5*1e5+10;
char s[N];
int n, T, root, last, sz;
int ch[N<<1][26], par[N<<1], sum[N<<1], num[N<<1], len[N<<1], b[N<<1], cnt[N];
void add_sam(int c){
int cur = ++sz, p = last;
len[cur] = len[p] + 1; num[cur] = 1;
//num表示该子串在不同位置出现的次数
for(; p&&!ch[p][c]; p=par[p]) ch[p][c] = cur;
if( !p ) par[cur] = root;
else{
int q = ch[p][c];
if(len[p]+1 == len[q]) par[cur] = q;
else{
int nq = ++sz;//虚拟节点
memcpy(ch[nq], ch[q], sizeof(ch[nq]));
len[nq] = len[p] + 1;
par[nq] = par[q];
par[cur] = par[q] = nq;
for(; p&&ch[p][c]==q; p=par[p]) ch[p][c] = nq;
}
}
last = cur;
}
void get_sum() {
for(int i=1; i<=sz; i++) cnt[len[i]]++;
for(int i=1; i<=n; i++) cnt[i] += cnt[i-1];
for(int i=sz; i>=1; i--) b[cnt[len[i]]--] = i;//基数排序len
for(int i=sz; i>=1; i--)//len相当于deep
if( !T ) num[b[i]] = 1;//不统计不同位置相同串
else num[par[b[i]]] += num[b[i]];//统计不同位置相同串
num[1] = 0;//
for(int i=sz; i>=1; i--){
int p = b[i]; sum[p] = num[p];//先计算自己(分情况看是否统计不同位置相同串)
for(int j=0; j<26; j++)//sum[p]表示p之后能转移到sum[p]个节点
//p之后能转移到的节点都比p大(或相等),所以sum[p]也表示p是第sum[p]大的串
sum[p] += sum[ch[p][j]];
}
}
int main() {
scanf("%s", s); n = strlen(s);
root = last = ++sz;//root为1
for(int i=0; i<n; i++) add_sam(s[i]-'a');
int k;scanf("%d%d", &T, &k);
get_sum();
if(k > sum[1]){ printf("-1"); return 0; }
int p = root;
while(k > num[p]){//当前节点不满足条件
for(int c=0; c<26; c++)//遍历子节点
if( ch[p][c] ){
if(sum[ch[p][c]] >= k){//此子树中有满足条件的点
printf("%c", 'a' + c);//既然要进入此子树,那么一定有'a'+c
k -= num[p]; p = ch[p][c];//进入子树中
break;
}
else k -= sum[ch[p][c]];//跳过这一子树
}
}
return 0;
}