SPOJ - SUBLEX 后缀自动机

解题思路:不重复的计算子串很简单,只要记忆化搜索,dp过去求每个节点后面有多少个串,然后用逼近法,去求出那个串就行了。


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mx = 2e5 + 10;
char str[mx],Ts[mx];
struct state
{
	int fa,len;//fa为包含该转态的最相近节点,len是从根节点走到该转态的最长长度 
	int id[26];	
}Tr[mx];
int tot,last,ans,v[mx],pos[mx],cnt[mx];
int n,m;
//后缀自动机定义,从根节点出发到达任意一个节点形成的串都是唯一的,且一定是母串的子串 
void suf_auto(int ch)
{
	int np = ++tot,p = last;
	//其实last就是原来的一条直串扩展到的前一个位置
	//比如abccbdab这个串,自动机上肯定存在最长的一个路径就是这个串,那么last就是一直在这上面的
	Tr[np].len = Tr[last].len + 1;
	last = np;
	//for(;p&&!Tr[p].id[ch];p=Tr[p].fa) Tr[p].id[ch] = np;
	while(p&&!Tr[p].id[ch]) Tr[p].id[ch] = np, p = Tr[p].fa; 
	//找到包含p转态的节点,那么它也可以转移到新转态np
	//可以看出是短串包含长串 
	if(!p) Tr[np].fa = 1;//如果都没有包含p状态的节点有可以扩展出ch这个转态,那么新状态被根节点包含 
	else{
		int q = Tr[p].id[ch];
		//如果if成立,不存在另一个点t使得Tr[t].len + 1==Tr[q].len证明也很简单 
		if(Tr[p].len+1==Tr[q].len) Tr[np].fa = q;//说明q状态可以包含新状态np 
		else{
			int nq = ++tot; 
			Tr[nq] = Tr[q];//分裂出q转态,就是分裂出if判断那个节点
			Tr[nq].len = Tr[p].len + 1;
			Tr[q].fa = Tr[np].fa = nq;//分裂出的nq肯定可以包含原来的点q和np 
			while(p&&Tr[p].id[ch]==q) Tr[p].id[ch] = nq,p = Tr[p].fa;
			//将后面包含p的点的儿子是q的也变成分裂的那个,因为他们就是跟着p走的,分裂出来的nq自然是p直接扩展状态 
		}
	}
}
void init()//初始化根节点为1号 
{
	last = tot = 1; 
	Tr[1].len = Tr[1].fa = 0;
	//memset(Tr[1].id,0,sizeof(Tr[1].id));
	//这里注意多个数据id数组要初始化 
}
void dfs(int x)
{
	cnt[x] = 1;
	for(int i=0;i<26;i++){
		if(!Tr[x].id[i]) continue;
		if(!cnt[Tr[x].id[i]]) dfs(Tr[x].id[i]);
		cnt[x] += cnt[Tr[x].id[i]];
	}
}
void find(int x)
{
	int ret=0,p = 1;
	while(x){
		for(int i=0;i<26;i++){
			if(x>cnt[Tr[p].id[i]]) x -= cnt[Tr[p].id[i]];
			else{
				putchar(i+'a');
				x--,p = Tr[p].id[i];
				break;
			}
		}
	}
}
int main()
{
	init();
	scanf("%s",str);
	int len = strlen(str);
	for(int i=0;i<len;i++) suf_auto(str[i]-'a');
	dfs(1);
	scanf("%d",&m);
	while(m--){
		scanf("%d",&n);
		find(n);
		puts("");	
	}
	return 0;
} 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值