[TJOI2015]弦论(第k小子串)

题意:
对于一个给定的长度为n的字符串,求出它的第k小子串。
有参数t,t为0则表示不同位置的相同子串算作一个,t为1则表示不同位置的相同子串算作多个。

题解:
首先,因为t的原因,后缀数组较难实现,这里不讨论。

使用后缀自动机:
因为,这里需要按字典序考虑子串,所以要使用trs指针。
首先,计算出每个子串的贡献:t=0则为1,t=1则为出现次数。
然后,通过记搜算出匹配到每个点之后可以形成多少贡献。因为使用trs,无需考虑压缩。
最后,在每个节点处找到唯一一个应当向下计算的点,循环直到找到解。

代码:

#include <stdio.h>
#define ll long long
int trs[1000010][26],fa[1000010],len[1000010],sl=1,la=1;
int su[1000010];ll dp[1000010];
void insert(int c)
{
	int np=++sl,p=la;
	len[np]=len[la]+1;la=np;
	while(p!=0&&trs[p][c]==0)
	{
		trs[p][c]=np;
		p=fa[p];
	}
	if(p==0)
		fa[np]=1;
	else
	{
		int q=trs[p][c];
		if(len[q]==len[p]+1)
			fa[np]=q;
		else
		{
			int nq=++sl;
			len[nq]=len[p]+1;
			fa[nq]=fa[q];fa[q]=fa[np]=nq;
			for(int i=0;i<26;i++)
				trs[nq][i]=trs[q][i];
			while(p!=0&&trs[p][c]==q)
			{
				trs[p][c]=nq;
				p=fa[p];
			}
		}
	}
	su[la]=1;
}
int fr[1000010],ne[1000010],v[1000010],bs=0;
void addb(int a,int b)
{
	v[bs]=b;
	ne[bs]=fr[a];
	fr[a]=bs++;
}
void build()
{
	for(int i=1;i<=sl;i++)
		fr[i]=-1;
	for(int i=2;i<=sl;i++)
		addb(fa[i],i);
}
void dfs0(int u)
{
	for(int i=fr[u];i!=-1;i=ne[i])
	{
		dfs0(v[i]);
		su[u]|=su[v[i]];
	}
}
void dfs1(int u)
{
	for(int i=fr[u];i!=-1;i=ne[i])
	{
		dfs1(v[i]);
		su[u]+=su[v[i]];
	}
}
void dfs2(int u)
{
	if(dp[u])
		return;
	dp[u]=su[u];
	for(int i=0;i<26;i++)
	{
		if(trs[u][i])
		{
			dfs2(trs[u][i]);
			dp[u]+=dp[trs[u][i]];
		}
	}
}
char zf[500010];
int main()
{
	int t,k,u=1;
	scanf("%s%d%d",zf,&t,&k);
	for(int i=0;zf[i]!=0;i++)
		insert(zf[i]-'a');
	build();
	if(t==0)dfs0(1);
	else dfs1(1);
	dfs2(1);
	if(dp[1]-su[1]<k)
	{
		printf("-1");
		return 0;
	}
	while(1)
	{
		if(u!=1)
		{
			if(su[u]>=k)
				break;
			k-=su[u];
		}
		int i;
		for(i=0;i<26;i++)
		{
			if(trs[u][i]==0)
				continue;
			if(dp[trs[u][i]]>=k)
				break;
			k-=dp[trs[u][i]];
		}
		printf("%c",i+'a');
		u=trs[u][i];
	}
	return 0;
}
原文地址:https://www.cnblogs.com/lnzwz/p/11616301.html