BZOJ 3998: [TJOI2015]弦论【后缀自动机】

题面:

对于一个给定长度为N的字符串,求它的第K小子串是什么。
Input
第一行是一个仅由小写英文字母构成的字符串S
第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个。T=1则表示不同位置的相同子串算作多个。
N<=500000

题目分析:

后缀自动机神奇的性质。。
不同位置的算多个,那么除了根节点每个点代表其终点集合个串。
不同位置的算一个,那么除了根节点每个点代表一个串(包括克隆出的点)。
想想似乎非常的有道理

Code:

#include
#include
#include
#define maxn 1000005
#define maxc 26
using namespace std;
const int inf = 0x3f3f3f3f;
int n,T,K,sum[maxn],b[maxn],sa[maxn];
char a[maxn];
int fail[maxn]={-1},ch[maxn][maxc],len[maxn],cnt[maxn],sz,last;
void sa_extend(int c){
	int p=last,cur=++sz,q;
	len[last=cur]=len[p]+1,cnt[cur]=1;
	for(;p!=-1&&!ch[p][c];p=fail[p]) ch[p][c]=cur;
	if(p==-1) fail[cur]=0;
	else if(len[p]+1==len[q=ch[p][c]]) fail[cur]=q;
	else{
		int clone=++sz; len[clone]=len[p]+1,fail[clone]=fail[q];
		fail[q]=fail[cur]=clone;
		memcpy(ch[clone],ch[q],sizeof ch[q]);
		for(;p!=-1&&ch[p][c]==q;p=fail[p]) ch[p][c]=clone;
	}
}
int main()
{
	scanf("%s",a+1),n=strlen(a+1);
	for(int i=1;i<=n;i++) sa_extend(a[i]-'a');
	scanf("%d%d",&T,&K);
	for(int i=1;i<=sz;i++) b[len[i]]++;
	for(int i=1;i<=n;i++) b[i]+=b[i-1];
	for(int i=1;i<=sz;i++) sa[b[len[i]]--]=i;
	for(int i=sz;i>=1;i--) 
		if(T) cnt[fail[sa[i]]]+=cnt[sa[i]];
		else cnt[i]=1;
	cnt[0]=0;
	for(int i=sz,t;i>=0;i--){
		t=sa[i],sum[t]=cnt[t];
		for(int j=0;j<maxc;j++) if(ch[t][j]) sum[t]+=sum[ch[t][j]];
	}
	if(sum[0]<K) puts("-1");
	else{
		for(int p=0;K>0;K-=cnt[p])
			for(int c=0,v;c<maxc;c++){
				if(!(v=ch[p][c])) continue;
				if(sum[v]<K) K-=sum[v];
				else {putchar(c+'a'),p=v;break;}
			}
	}
}

你可能感兴趣的:(后缀自动机,字符串)