【luogu SP7258】SUBLEX - Lexicographical Substring Search

SUBLEX - Lexicographical Substring Search

题目链接:luogu SP7258

题目大意

给你一个字符串,要你求字典序第 k 大的子串。
(相同的子串算只一个)

思路

我们考虑用 SAM 来做。

考虑之前我们求出了 f i f_i fi,就是后缀自动机上从 i i i 出发的不同子串个数(包括空串)。

那我们考虑用这样的方式:(有点类似线段树上找第 k k k 个的位置)
从起点 1 1 1 开始,现在是 x x x,按字典序从小到大开始枚举要新加的字符,然后就在后缀自动机上走。
如果有这一条边 z z z 字符,连向 y y y,那你就看 f y f_y fy k k k 的关系。
如果 f y ≤ k f_y\leq k fyk,那就说明第 k k k 个的这一位是这个字符,就把这个字符输出,然后现在起点变成 y y y k − = 1 k-=1 k=1(因为要减去 y y y 这个代表的最长后缀,它有 z z z 这个字符,那我们现在去处理下一个,那它也过掉了)。
那如果 f y > k f_y>k fy>k,那就将 k − = f y k-=f_y k=fy

k = 0 k=0 k=0 的时候,你就可以直接退出了,此时 1 1 1 从后缀自动机上跳到你现在的点的路径所代表的子串就是你要的子串。

代码

#include<cstdio>
#include<cstring>
#define ll long long

using namespace std;

struct node {
	int len, fa, size;
	ll f;
	int son[31];
	node() {
		memset(son, 0, sizeof(son));
		len = fa = 0;
		f = -1ll;
	}
}d[200001];
int n, tot, lst, T, k;
char s[100001];

void SAM_build(int now) {
	int p = lst;
	int np = ++tot;
	d[np].len = d[p].len + 1;
	lst = np;
	
	for (; p && !d[p].son[now]; p = d[p].fa)
		d[p].son[now] = np;
	
	if (!p) d[np].fa = 1;
		else {
			int q = d[p].son[now];
			if (d[q].len == d[p].len + 1) d[np].fa = q;
				else {
					int nq = ++tot;
					d[nq] = d[q];
					d[nq].len = d[p].len + 1;
					d[q].fa = nq;
					d[np].fa = nq;
					for (; p && d[p].son[now] == q; p = d[p].fa) {
						d[p].son[now] = nq;
					}
				}
		}
}

void dfs(int now) {
	if (d[now].f != -1) return ;
	d[now].f = 0;
	for (int i = 0; i < 26; i++)
		if (d[now].son[i]) {
			dfs(d[now].son[i]);
			d[now].f += d[d[now].son[i]].f;
		}
	d[now].f++;
}

void work(int now, int num) {
	while (num) {
		for (int i = 0; i < 26; i++)
			if (d[now].son[i]) {
				if (d[d[now].son[i]].f >= num) {//再减就变成非正数,就说明排名在这里
					putchar(i + 'a');
					now = d[now].son[i];
					num--;//记得减去外面最大的串
					break;
				}
				else num -= d[d[now].son[i]].f;
			}
	}
}

int main() {
	scanf("%s", s + 1);
	n = strlen(s + 1);
	
	tot = lst = 1;
	for (int i = 1; i <= n; i++)
		SAM_build(s[i] - 'a');
	
	dfs(1);
	
	scanf("%d", &T);
	while (T--) {
		scanf("%d", &k);
		work(1, k);
		printf("\n");
	}
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值