bzoj 3998: [TJOI2015]弦论 (后缀自动机)

3998: [TJOI2015]弦论

Time Limit: 10 Sec   Memory Limit: 256 MB
Submit: 2024   Solved: 665
[ Submit][ Status][ Discuss]

Description

对于一个给定长度为N的字符串,求它的第K小子串是什么。

Input

 第一行是一个仅由小写英文字母构成的字符串S

第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个。T=1则表示不同位置的相同子串算作多个。K的意义如题所述。

Output

输出仅一行,为一个数字串,为第K小的子串。如果子串数目不足K个,则输出-1

Sample Input

aabc
0 3

Sample Output

aab

HINT

 N<=5*10^5


T<2

K<=10^9

Source

[ Submit][ Status][ Discuss]


题解:后缀自动机

这道题T=0的时候就是SPOJ7258.

T=1的时候因为位置不同的子串也算不同,所以我们需要知道每个状态的|right|.进行计算的时候初值不是1,而是当前点的|right|

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 1000003
using namespace std;
int n,m,cnt,last,p,q,nq,np,root,T;
int ch[N][30],fa[N],l[N],size[N],pos[N],v[N],r[N];
char s[N];
void extend(int x)
{
	int c=s[x]-'a';
	p=last; np=++cnt; last=np;
	l[np]=x;
	for (;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
	if (!p) fa[np]=root;
	else {
		q=ch[p][c];
		if (l[q]==l[p]+1) fa[np]=q;
		else {
			nq=++cnt; l[nq]=l[p]+1;
			memcpy(ch[nq],ch[q],sizeof ch[nq]);
			fa[nq]=fa[q];
			fa[q]=fa[np]=nq;
			for (;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
		}
	}
}
void solve()
{
	for (int i=cnt;i>=1;i--){
		int t=pos[i];
		if (t!=1) size[t]++;
		for (int j=0;j<26;j++) 
		  if (ch[t][j]) size[t]+=size[ch[t][j]];
	}
}
void solve1()
{
	p=1;
	for (int i=1;i<=n;i++) {
		p=ch[p][s[i]-'a'];
		r[p]++;
	}
	for (int i=cnt;i>=1;i--){
		int t=pos[i];
		r[fa[t]]+=r[t];
	}
	for (int i=cnt;i>=1;i--){
		int t=pos[i];
		if (t!=1) size[t]+=r[t];
		for (int j=0;j<26;j++)
		 if (ch[t][j]) size[t]+=size[ch[t][j]];
	}
}
void pri(int x)
{
	p=1;
	while (x){
		for (int i=0;i<26;i++) 
		if (ch[p][i])
		 if (x>size[ch[p][i]]) x-=size[ch[p][i]];
		 else {
		 	putchar(i+97);
		 	if (T==0) x--;
		 	else x-=r[ch[p][i]];
		 	p=ch[p][i];
		 	break;
		 }
		if (x<0) break;
	}
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%s",s+1);
	n=strlen(s+1); last=root=++cnt;
	for (int i=1;i<=n;i++) extend(i);
	scanf("%d%d",&T,&m);
	for (int i=1;i<=cnt;i++) v[l[i]]++;
	for (int i=1;i<=n;i++) v[i]+=v[i-1];
	for (int i=1;i<=cnt;i++) pos[v[l[i]]--]=i;
	if (T==0) solve();
	else solve1();
	//for (int i=1;i<=cnt;i++) cout<<r[i]<<" ";
	//cout<<endl;
	//for (int i=1;i<=cnt;i++) cout<<l[i]<<" ";
	//cout<<endl;
	if (m>size[1]) {
		printf("-1\n");
		return 0;
	}
	pri(m); printf("\n");
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值