BZOJ3998: [TJOI2015]弦论【SAM】

3998: [TJOI2015]弦论

【题目描述】

传送门

【题解】

先用SAM算出不同子串个数,然后二分查找就可以了(像平衡树一样的查找第K小方式)

【代码如下】

#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=5e5+5;
int n,TT,K,Sum[MAXN<<1],Hsh[MAXN<<1],Sa[MAXN<<1];char ch[MAXN];
struct SAM{
	int cnt,lst,Son[MAXN<<1][27],Len[MAXN<<1],Siz[MAXN<<1],Fa[MAXN<<1];
	SAM(){cnt=lst=1;}
	void Insert(int c){
		int fa=lst,p=++cnt;lst=cnt;Len[p]=Len[fa]+1;Siz[p]=1;
		for(;fa&&!Son[fa][c];fa=Fa[fa]) Son[fa][c]=p;
		if(!fa){Fa[p]=1;return;}int x=Son[fa][c];
		if(Len[fa]+1==Len[x]){Fa[p]=x;return;}
		int y=++cnt;Fa[y]=Fa[x],Fa[x]=Fa[p]=y;Len[y]=Len[fa]+1;
		for(int i=0;i<26;++i) Son[y][i]=Son[x][i];
		for(;fa&&Son[fa][c]==x;fa=Fa[fa]) Son[fa][c]=y;
	}
}T;
void DFS(int x,int k){
	if(k<=T.Siz[x]) return;k-=T.Siz[x];
	for(int j=0;j<26;++j)
	if(k<=Sum[T.Son[x][j]]){putchar(j+'a'),DFS(T.Son[x][j],k);break;}else k-=Sum[T.Son[x][j]];
}
int main(){
	scanf("%s",ch+1);n=strlen(ch+1);
	for(int i=1;i<=n;++i) T.Insert(ch[i]-'a');
	scanf("%d%d",&TT,&K);
	for(int i=1;i<=T.cnt;++i) ++Hsh[T.Len[i]];
	for(int i=1;i<=n;++i) Hsh[i]+=Hsh[i-1];
	for(int i=1;i<=T.cnt;++i) Sa[Hsh[T.Len[i]]--]=i;
	for(int i=T.cnt,p;p=Sa[i],i;--i) TT?T.Siz[T.Fa[p]]+=T.Siz[p]:T.Siz[p]=1;
	T.Siz[1]=0;
	for(int i=T.cnt,p;i;--i){
		Sum[p=Sa[i]]=T.Siz[p];
		for(int j=0;j<26;j++) Sum[p]+=Sum[T.Son[p][j]];
	}
	if(K>Sum[1]) return printf("-1\n"),0;return DFS(1,K),0;
}
posted @ 2019-03-14 20:50  XSamsara  阅读(126)  评论(0编辑  收藏  举报