【TJOI 2015】弦论

传送门


Problem

对于一个给定的长度为 n n n 的字符串,求出它的第 k k k 小子串。

还有一个参数 t t t t t t 0 0 0 则表示不同位置的相同子串算作一个, t t t 1 1 1 则表示不同位置的相同子串算作多个。


Solution

我们令 S i z e i Size_i Sizei p a r e n t _ t r e e \mathrm {parent\_tree} parent_tree i i i 子树的节点个数(除去克隆点)。

S i z e i Size_i Sizei 有一个性质,即 S i z e i Size_i Sizei i i i 所在的终点等价类的 e n d p o s \mathrm{endpos} endpos 集合大小相等。

因此当 t = 1 t=1 t=1 S i z e i Size_i Sizei 保持不变, t = 0 t=0 t=0 时将每个 S i z e i Size_i Sizei 设为 1 1 1(代表只算一次)。

于是我们就可以通过 S i z e Size Size 求出子串的总数,从而用试填发法的思想确定第 k k k 小字符串。


Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1000005
#define ll long long
using namespace std;
char S[N];
int l,t,last,tot;
int A[N],Size[N];
ll k,sum[N];
struct SAM{
	int len,link,nxt[30];
}a[N<<1];
void Insert(int c){
	int p,cur=++tot;
	a[cur].len=a[last].len+1,Size[cur]=1;
	for(p=last;p!=-1&&!a[p].nxt[c];p=a[p].link)  a[p].nxt[c]=cur;
	if(p==-1)  a[cur].link=0;
	else{
		int now=a[p].nxt[c];
		if(a[now].len==a[p].len+1)
			a[cur].link=now;
		else{
			int Clone=++tot;
			a[Clone]=a[now],a[Clone].len=a[p].len+1;
			for(;p!=-1&&a[p].nxt[c]==now;p=a[p].link)
				a[p].nxt[c]=Clone;
			a[cur].link=a[now].link=Clone;
		}
	}
	last=cur;
}
void solve(int x,int k){
	if(k<=Size[x])  return;
	k-=Size[x];
	for(int i=0;i<26;++i){
		int to=a[x].nxt[i];
		if(!to)  continue;
		if(k>sum[to])  {k-=sum[to];continue;}
		putchar(i+'a'),solve(to,k);return;
	}
}
int main(){
	a[0].link=-1;
	scanf("%s%d%lld",S+1,&t,&k);
	int i,j,l=strlen(S+1);
	for(i=1;i<=l;++i)  Insert(S[i]-'a');
	for(i=1;i<=tot;++i)  sum[a[i].len]++;
	for(i=1;i<=tot;++i)  sum[i]+=sum[i-1];
	for(i=1;i<=tot;++i)  A[sum[a[i].len]--]=i;
	for(i=tot;i>=0;--i)
		(t?(Size[a[A[i]].link]+=Size[A[i]]):(Size[A[i]]=1)),sum[A[i]]=Size[A[i]];
	sum[0]=Size[0]=0;
	for(i=tot;i>=0;--i)
		for(j=0;j<26;++j)
			if(a[A[i]].nxt[j])
				sum[A[i]]+=sum[a[A[i]].nxt[j]];
	if(sum[0]<k)  puts("-1");
	else  solve(0,k);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值