Description
对于一个给定长度为N的字符串,求它的第K小子串是什么。
Input
第一行是一个仅由小写英文字母构成的字符串S
Output
输出仅一行,为一个数字串,为第K小的子串。如果子串数目不足K个,则输出-1
Sample Input
0 3
Sample Output
HINT
N<=5*10^5
T<2
K<=10^9
Source
分析:
如果
T=0
,后缀数组的做法很经典
按照所有后缀的字典序依次加入后缀,新加入一个后缀会产生“这个后缀的长度减去这个后缀的height值”这么多个子串
显然每次新产生的子串比之前产生的所有子串的字典序都大,那么就可以在前缀和上二分了
预处理
O(nlogn)
,单组询问
O(logn)
liu_runda大佬给出了
T=1
时的后缀数组解法:
显然每个后缀都会产生这个后缀的长度这么多个子串,但这并没有什么用
我们不妨考虑把一个子串的多次出现全都算到这个子串在所有出现的后缀中字典序最小的那个后缀上
那么只需按照height从大到小合并,并查集维护一下就可以了
然后就可以前缀和+二分找出第k大的子串在是哪一个后缀所产生的字典序第几大的子串
这之后我们还需要支持查询某个后缀产生的子串中第k大的串是多少
于是强行又写了一遍按height合并所有后缀,预处理
O(nlogn)+
单组询问
O(nlogn)
但是我就是想没事找事,感觉lrd的方法也不是很科学,而且我也不是很懂
用
SAM
怎么解决上面问题呢
首先当然是建一个
SAM
出来
SAM
的一个很有用性质:搜索
SAM
可以得到字符串所有的子串
而这些字符串是有字典序的,且互不重复的
我们先统计一下每一个串出现了多少次: sizei
- 对于不计重复的情况下: sizei =1
- 对于计算重复的情况下:在
SAM
上,
parent
代表的串一定是
son
的子串
那么 son 的出现就意味着 parent 的出现, sizeparent+=sizeson
但是光一个 size 数组是不够的,我们还需要统计出从每个点出发后都有多少串
比如
S=ababab,aba
这个子串最后可以走到
abab,ababab
,于是
sum
为2
初值是所有结点的
sumi=sizei,sumi=∑sumson
之后我们就可以在
SAM
上构造答案了
我们从
root
开始,从
a
到
如果
sum[ch[now][x]]>K
,即往
x
这个方向走后继不足
以此类推,构造答案
tip
感觉
首先ta的原理我就没有完全明白,应用起来不是特别理解
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=50003;
int T,K,len,root=1,sz=1,last=1,fa[N<<1],dis[N<<1],ch[N<<1][26];
int sum[N<<1],size[N<<1],pos[N<<1],c[N<<1];
char s[N];
void insert(int x)
{
int now=++sz,pre=last;
last=now; size[now]=1;
dis[now]=dis[pre]+1;
for (;pre&&!ch[pre][x];pre=fa[pre]) ch[pre][x]=now;
if (!pre) fa[now]=root;
else
{
int q=ch[pre][x];
if (dis[q]==dis[pre]+1) fa[now]=q;
else
{
int nows=++sz;
dis[nows]=dis[pre]+1;
memcpy(ch[nows],ch[q],sizeof(nows));
fa[nows]=fa[q]; fa[q]=fa[now]=nows;
for (;pre&&ch[pre][x]==q;pre=fa[pre]) ch[pre][x]=nows;
}
}
}
void work()
{
for (int i=1;i<=sz;i++) c[dis[i]]++;
for (int i=1;i<=len;i++) c[i]+=c[i-1];
for (int i=1;i<=sz;i++) pos[c[dis[i]]--]=i;
for (int i=sz;i>=1;i--)
if (T) size[fa[pos[i]]]+=size[pos[i]]; //不同位置的相同子串算作多个
else size[pos[i]]=1;
size[root]=0;
for (int i=sz;i>=1;i--)
{
sum[pos[i]]=size[pos[i]];
for (int j=0;j<26;j++)
if (ch[pos[i]][j])
sum[pos[i]]+=sum[ch[pos[i]][j]];
}
}
int main()
{
scanf("%s",s+1); len=strlen(s+1);
for (int i=1;i<=len;i++) insert(s[i]-'a');
scanf("%d%d",&T,&K);
work();
if (K>sum[root]) {
printf("-1\n");
return 0;
}
int now=root;
while ((K-=size[now])>0) //size 当前状态的出现次数
{
int p=0;
while (K>sum[ch[now][p]]) K-=sum[ch[now][p++]];
now=ch[now][p]; //按照字典序遍历
printf("%c",'a'+p);
}
return 0;
}