Time Limits: 1000 ms Memory Limits: 262144 KB
Description
Input
Output
Sample Input
输入1:
aabc
0 3
输入2:
aabc
1 3
输入3:
aabc
1 11
Sample Output
输出1:
aab
输出2:
aa
输出3:
-1
Data Constraint
Solution
本题要求出长度为n的字符串S中的第k小子串。
分为不同位置的相同子串算一个和算多个两种。
对于10%的数据,n ≤ 1000,所以可以用 S S 的所有后缀搭建一棵字典树。字典树上的每个节点都代表了一个子串。以该节点为根的子树中叶子节点的个数等同于该节点所表示的子串在字符串中出现的次数。 若相同子串只计算一次,那么直接在字典树上 ,第 k k 个被访问到的节点即为第 小子串。 若相同子串要计算多次,同样在字典树上进行 dfs d f s 即可,但是要注意每个节点不止代表一个子串。 总复杂度为 O(n2) O ( n 2 ) 。
对于50%的数据,t = 0,即仅询问相同子串只计算一次的情况。由于
n
n
较大,所以不能搭建字典树。可以使用后缀数组来解决。 构建后缀数组,求出 三个数组后,由于子串都是后缀的前缀,所以其实已经把所有子串排好序了。 然后可以使用二分答案,或者按照排好的顺序线性扫描一遍所有的后缀,即可算出第k小子串为哪个后缀的第几个前缀,进而求出答案。 若使用倍增,总复杂度为
O(nlogn)
O
(
n
l
o
g
n
)
;若使用DC3,总复杂度为
O(n)
O
(
n
)
。(然而。。。我不会)
DC3,了解一下
对于100%的数据,使用后缀自动机来解决。后缀自动机可以理解为后缀树的压缩版本,搭建后即可像后缀树一样进行dfs。 为了减少 dfs d f s 所花费的时间,可以提前进行一个小 dp d p ,求出每个节点向下所有的节点能代表的子串个数(对应题目要求有相同子串算一个和相同子串算多个两种)这样总复杂度为O(n)。
Code
#include<algorithm>
#include<cstring>
#include<cstdio>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fd(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
const int N=5e5+5,M=3*N;
int n,o,k,tot,mx[M],fail[M],t[M][26];
int num,last[M],size[M];
int q[M],ru[M],sum[M];
char s[N];
struct edge
{
int to,next;
}e[M];
void SAM()
{
int ls=1;
fo(i,1,n)
{
int p=ls,c=s[i]-'a';
mx[ls=++tot]=i,size[tot]=1;
while(p&&!t[p][c]) t[p][c]=ls,p=fail[p];
if(!p) fail[ls]=1;
else
{
int q=t[p][c];
if(mx[q]==mx[p]+1) fail[ls]=q;
else
{
fail[++tot]=fail[q];
fail[ls]=fail[q]=tot;
fo(j,0,25) t[tot][j]=t[q][j];
mx[tot]=mx[p]+1;
while(p&&t[p][c]==q) t[p][c]=tot,p=fail[p];
}
}
}
}
void link(int x,int y)
{
e[++num]=(edge){y,last[x]},last[x]=num;
}
void dfs(int x)
{
for(int w=last[x];w;w=e[w].next)
dfs(e[w].to),size[x]+=size[e[w].to];
}
int calc()
{
if(o)
{
fo(i,2,tot) link(fail[i],i);
dfs(1),size[1]=0;
}
else fo(i,2,tot) size[i]=1;
fo(i,1,tot) fo(j,0,25) if(t[i][j]) ++ru[t[i][j]];
int l=0,r=1;
q[1]=1;
while(l<r)
{
int x=q[++l];
fo(j,0,25) if(t[x][j])
{
--ru[t[x][j]];
if(!ru[t[x][j]]) q[++r]=t[x][j];
}
}
fd(i,tot,1)
{
fo(j,0,25) sum[q[i]]+=sum[t[q[i]][j]];
sum[q[i]]+=size[q[i]];
}
if(k>sum[1]) return 0;
int x=1;
while(1)
{
if(size[x]>=k) return 1;
int gs=size[x],c;
for(c=0;c<26;c++)
if(gs+sum[t[x][c]]<k) gs+=sum[t[x][c]];
else break;
putchar(c+'a');
k-=gs,x=t[x][c];
}
}
int main()
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
scanf("%s",s+1),n=strlen(s+1);
scanf("%d%d",&o,&k);
tot=1,SAM();
if(!calc()) printf("-1");
}