3998: [TJOI2015]弦论
Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 3779 Solved: 1375
[ Submit][ Status][ Discuss]
Description
对于一个给定长度为N的字符串,求它的第K小子串是什么。
Input
第一行是一个仅由小写英文字母构成的字符串S
第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个。T=1则表示不同位置的相同子串算作多个。K的意义如题所述。
Output
输出仅一行,为一个数字串,为第K小的子串。如果子串数目不足K个,则输出-1
Sample Input
aabc
0 3
0 3
Sample Output
aab
HINT
N<=5*10^5
T<2
K<=10^9
相对来说比较简单的一道题目。
求排名第k大的子串。显然,既然是所有子串,那么显然用后缀自动机,这个可以在里面保存所有子串的东西。然后就是考虑第k大。我们知道自动机里面的每一个节点表示一个状态,它的最大长度对应一个子串。然后所有的parent指针指向它的状态都是包含它的。所以说,如果那些状态出现,这个状态一定会出现,因此可以对于T不同的情况讨论。如果T等于0,那么只要它在parent树中有儿子或者本事就是关键点,那么该点表示的串出现次数为1;如果T等于1,那么该点表示串出现次数为本身的值与儿子出现次数之
和。
如此,我们求出来了每个点表示串的出现次数,有些人称之为right数组。然后我们考虑如何解决这个K大的问题。后缀自动机本身就是包含字典树的存储方式,所以想要做到字典序也是很简单。所以,如果对于每一个状态,我们知道与该点前缀相同的所有子串个数,那么就可以用dfs定位这个第k大。而求这个前缀相同的子串个数,也是很容易的,对于一个节点直接求它所有儿子的right数组之和即可,注意这个儿子不是parent树中的儿子,而是自动机中的后继边。之后的dfs就很简单了。还有关于实现方法,用基数排序之后的序列从后往前,是符合dfs顺序且这么做比dfs快。具体见代码:
#include<bits/stdc++.h>
#define N 1000010
using namespace std;
int t,k,tot,n,cnt[N],sum[N];
char s[N];
struct Suffix_Automation
{
int tot,cur,c[N],sa[N];
struct node{int ch[26],len,fa;} T[N];
void init(){cur=tot=1;memset(T,0,sizeof(T));}
void ins(int x,int id)
{
int p=cur;cur=++tot;T[cur].len=id; cnt[cur]++;
for(;p&&!T[p].ch[x];p=T[p].fa) T[p].ch[x]=cur;
if (!p) {T[cur].fa=1;return;}int q=T[p].ch[x];
if (T[p].len+1==T[q].len) {T[cur].fa=q;return;}
int np=++tot; memcpy(T[np].ch,T[q].ch,sizeof(T[q].ch));
T[np].fa=T[q].fa; T[q].fa=T[cur].fa=np; T[np].len=T[p].len+1;
for(;p&&T[p].ch[x]==q;p=T[p].fa) T[p].ch[x]=np;
}
void getsa()
{
for(int i=2;i<=tot;i++) c[T[i].len]++;
for(int i=1;i<=n;i++) c[i]+=c[i-1];
for(int i=tot;i>=1;i--) sa[c[T[i].len]--]=i;
}
} SAM;
void find_kth(int x,int k)
{
if (k<=cnt[x]) return;
k-=cnt[x];
for(int i=0;i<26;i++)
{
if (int y=SAM.T[x].ch[i])
{
if (k<=sum[y])
{
putchar(i+'a');
find_kth(y,k);
return;
} else k-=sum[y];
}
}
}
int main()
{
SAM.init();
scanf("%s",s);
scanf("%d%d",&t,&k);
for(int i=0;s[i];i++)
SAM.ins(s[i]-'a',i+1);
n=strlen(s); SAM.getsa();
for(int i=SAM.tot-1;i>=0;i--)
{
if (!t) cnt[SAM.sa[i]]=1;
cnt[SAM.T[SAM.sa[i]].fa]+=cnt[SAM.sa[i]];
}
cnt[1]=0;
for(int i=SAM.tot-1;i>=0;i--)
{
sum[SAM.sa[i]]=cnt[SAM.sa[i]];
for(int j=0;j<26;j++)
sum[SAM.sa[i]]+=sum[SAM.T[SAM.sa[i]].ch[j]];
}
if (sum[1]<k){puts("-1");return 0;}
find_kth(1,k);
}