【洛谷】P3975 [TJOI2015]弦论(SAM)

原题链接

题意

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

数据范围

1n1050t11k109

t0 则表示不同位置的相同子串算作一个,t1 则表示不同位置的相同子串算作多个。

思路

考虑后缀自动机(下文默认已经知晓 SAM 的基本性质)。

根据题意,当 t=0 时,SAM 上的每个节点的 Endpos 集合大小(该集合代表该节点对应的所有子串在原串中所有出现的位置)都为 1。反之则可以根据 parent 树的性质从下往上递推。定义 i 点的 Endpos 集合大小为 siz[i]

定义 sum[i] 表示经过 i 节点的子串数目(严格来说并不是,往后看就明白了)。即当前节点的 siz 加上以当它为前缀的所有节点的 siz。可以在 SAM 的 DAG 上逆序递推。需要注意的是,这里不需要考虑 i 节点对应的所有子串,因为接下来查询的时候根据字典序的性质是从前往后推的,也就是一个前缀不断往后扩展,而不是后缀往前扩展(举个简单的例子,当前节点对应的子串集合为 {aabb,abb,bb},那么后面查询到这里时可能是从 aab 过来的,也可能是从 b 过来的,此时就与对应的其他子串无关了)。

查询的时候就是从根节点往后推,根据当前节点的 sizsum 判断是否要继续往后推,具体实现可以见代码,这部分放在代码里将更清晰。

code

#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e6+10;
#define LL long long
struct SAM{int ch[26],len,fa;}node[N];
int c[N],last=1,tot=1,t,k,num[N];LL siz[N],sum[N]; char s[N];
void extend(int c)
{
	int p=last,np=last=++tot;node[np].len=node[p].len+1;siz[np]=1;
	while(p&&!node[p].ch[c]) node[p].ch[c]=np,p=node[p].fa;
	if(!p) node[np].fa=1;
	else
	{
		int q=node[p].ch[c];
		if(node[p].len+1==node[q].len) node[np].fa=q;
		else
		{
			int nq=++tot;node[nq]=node[q];node[nq].len=node[p].len+1;
			node[q].fa=node[np].fa=nq;
			while(p&&node[p].ch[c]==q) node[p].ch[c]=nq,p=node[p].fa;
		}
	}
}
void print(int now,int k) //k表示当前还要查询的第k小子串 
{
	if(k<=siz[now]) return ; //如果当前节点出现次数不小于k,就代表查询子串就是当前已经走到的子串,不需要往后推 
	k-=siz[now];
	for(int i=0;i<26;i++) //根据字典序的性质, 从小到大枚举当前位字符 
	{
		int t=node[now].ch[i];if(!t) continue;
		if(sum[t]<k) k-=sum[t];//有点类似于splay查询右子树时减去左子树的size 
		else {printf("%c",i+'a');print(t,k);return ;}
	}
}
int main()
{
	scanf("%s%d%d",s+1,&t,&k);for(int i=1;s[i];i++) extend(s[i]-'a');
	for(int i=1;i<=tot;i++) c[node[i].len]++;//求siz和sum数组可以按照这里的基数排序的方式,也可以直接建图dfs 
	for(int i=1;i<=tot;i++) c[i]+=c[i-1];
	for(int i=1;i<=tot;i++) num[c[node[i].len]--]=i;
	for(int i=tot;i>=1;i--) siz[node[num[i]].fa]+=siz[num[i]];
	for(int i=1;i<=tot;i++) t?sum[i]=siz[i]:siz[i]=sum[i]=1;
	siz[1]=sum[1]=0;//需要注意,根节点没有任何信息,下面的sum[1]是判断无解 
	for(int i=tot;i>=1;i--)
	    for(int j=0;j<26;j++) 
	        sum[num[i]]+=sum[node[num[i]].ch[j]];
	if(sum[1]<k) puts("-1");
	else print(1,k),puts("");
	return 0;
}```
posted @   曙诚  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示