【luogu U142356】勇者的后缀(SA)(主席树)(二分)

勇者的后缀

题目链接:luogu U142356

题目大意

给你一个字符串,每次询问给你 i,l,r 要你求所有 l~r 为起点的后缀中哪一个跟 i 为起点的后缀的最长公共前缀最长。
如果有多个一样长的,输出字典序最小的。

思路

首先考虑 SA / SAM。
然后如果是 SA,那两个后缀之间的最长公共前缀就是 \(\rm rnk\) 区间中的 \(\min\)
如果是 SAM,那就是 fail 树上的 LCA(反着建)

然后考虑怎么找 \(\min\) 中最大的或者 LCA 最下面的。(其实会发现大概是同一种)
那因为是 \(\min\),自然是短的时候才会出现(LCA 其实最下面的也是要在 dfs 序最近的)
所以我们就找到它的前驱后继。

然而我们只能找 \(l\sim r\) 的,所以我们可以用主席树来维护找。
(然后找前驱后继有一个简单的方法是先找它前面一共有多少个 \(k\),然后前驱就是定位到第 \(k-1\) 个,同理后继就是 \(k\)
(什么为啥总是大家都知道的我不知道,流眼泪的了)

然后发现如果答案相同要选字典序最小的。
考虑怎么搞,首先两个答案比一比,如果右边比左边优那右边肯定是右边最优的(因为都是按字典序排的,SAM 的 dfs 序其实也是字典序)

那左边的呢?
那我们就二分找到最左边的,然后就用那个即可。

代码

#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 2e5 + 100; 
int n, ans1[N], ans2[N], ss[N];
char s[N];
struct Ask {
	int id, l, r;
};
vector <Ask> q[N];

int sa[N], xx[N], yy[N], fir[N], log2_[N];
int height[N][21], rnk[N], tong[N], kind, ynum;

void Sort(int m, int *x, int *y) {
	for (int i = 1; i <= m; i++) tong[i] = 0;
	for (int i = 1; i <= n; i++) tong[x[i]]++;
	for (int i = 2; i <= m; i++) tong[i] += tong[i - 1];
	for (int i = n; i >= 1; i--) sa[tong[fir[i]]--] = y[i];
}

void SA(int *r, int *sa, int n, int m) {
	int *x = xx, *y = yy;
	for (int i = 1; i <= n; i++) x[i] = r[i] + 1;
	for (int i = 1; i <= n; i++) y[i] = i;
	for (int i = 1; i <= n; i++) fir[i] = x[y[i]];
	Sort(m, x, y);
	
	for (int j = 1; j < n; j <<= 1) {
		ynum = 0;
		for (int i = n - j + 1; i <= n; i++)
			y[++ynum] = i;
		for (int i = 1; i <= n; i++)
			if (sa[i] > j) y[++ynum] = sa[i] - j;
		for (int i = 1; i <= n; i++) fir[i] = x[y[i]];
		Sort(m, x, y);
		
		swap(x, y);
		kind = 1;
		x[sa[1]] = 1;
		for (int i = 2; i <= n; i++)
			if (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + j] == y[sa[i - 1] + j]) x[sa[i]] = kind;
				else x[sa[i]] = ++kind;
		
		if (kind == n) break;
		m = kind;
	}
}

void get_height(int *r, int *sa, int n) {
	int k = 0, j;
	for (int i = 1; i <= n; i++) rnk[sa[i]] = i;
	for (int i = 1; i <= n; i++) {
		if (k) k--;
		j = sa[rnk[i] - 1];
		while (r[i + k] == r[j + k] && i + k <= n && j + k <= n) k++;
		height[rnk[i]][0] = k;
	}
}

void get_RMQ(int n) {
	log2_[0] = -1; for (int i = 1; i <= n; i++) log2_[i] = log2_[i >> 1] + 1;
	for (int i = 1; i <= 20; i++)
		for (int j = 1; j + (1 << i) - 1 <= n; j++)
			height[j][i] = min(height[j][i - 1], height[j + (1 << (i - 1))][i - 1]);
}

int RMQ_sa(int l, int r) {
	if (!l || !r) return 0;
	if (l == r) return n - l + 1;
	l = rnk[l]; r = rnk[r]; if (l > r) swap(l, r);
	l++; int k = log2_[r - l + 1];
	return min(height[l][k], height[r - (1 << k) + 1][k]);
}

int RMQ_rnk(int l, int r) {
	if (!l || !r) return 0;
	if (l == r) return n - sa[l] + 1;
	if (l > r) swap(l, r);
	l++; int k = log2_[r - l + 1];
	return min(height[l][k], height[r - (1 << k) + 1][k]);
}

struct XD_tree {
	int ls[N << 6], rs[N << 6], sum[N << 6], tot;
	
	int copy(int x) {
		int now = ++tot; ls[now] = ls[x]; rs[now] = rs[x]; sum[now] = sum[x]; return now;
	}
	
	void insert(int &now, int l, int r, int pl) {
		now = copy(now); sum[now]++;
		if (l == r) return ; int mid = (l + r) >> 1;
		if (pl <= mid) insert(ls[now], l, mid, pl); else insert(rs[now], mid + 1, r, pl);
	}
	
	int get_sum(int now1, int now2, int l, int r, int L, int R) {
		if (!(sum[now2] - sum[now1])) return 0;
		if (L <= l && r <= R) return sum[now2] - sum[now1];
		int mid = (l + r) >> 1, re = 0;
		if (L <= mid) re += get_sum(ls[now1], ls[now2], l, mid, L, R);
		if (mid < R) re += get_sum(rs[now1], rs[now2], mid + 1, r, L, R);
		return re;
	}
	
	int get_k(int now1, int now2, int l, int r, int k) {
		if (l == r) return l;
		int mid = (l + r) >> 1;
		if (k <= sum[ls[now2]] - sum[ls[now1]]) return get_k(ls[now1], ls[now2], l, mid, k);
			else return get_k(rs[now1], rs[now2], mid + 1, r, k - (sum[ls[now2]] - sum[ls[now1]]));
	}
}T;
int rt[N];

int main() {
	scanf("%s", s + 1); n = strlen(s + 1);
	for (int i = 1; i <= n; i++) ss[i] = s[i] - 'a';
	
	SA(ss, sa, n, n);
	get_height(ss, sa, n);
	get_RMQ(n);
	for (int i = 1; i <= n; i++)
		rt[i] = rt[i - 1], T.insert(rt[i], 1, n, rnk[i]);
	
	int Q; scanf("%d", &Q);
	for (int i = 1; i <= Q; i++) {
		int x, l, r; scanf("%d %d %d", &x, &l, &r);
		q[x].push_back((Ask){i, l, r});
	}
	for (int i = 1; i <= n; i++) {
		int x = rnk[i];
		for (int j = 0; j < q[i].size(); j++) {
			int id = q[i][j].id, l = q[i][j].l, r = q[i][j].r;
			int num = T.get_sum(rt[l - 1], rt[r], 1, n, 1, x - 1), L = 0, R = 0;
			if (num) L = T.get_k(rt[l - 1], rt[r], 1, n, num);
			if (num < T.sum[rt[r]] - T.sum[rt[l - 1]]) R = T.get_k(rt[l - 1], rt[r], 1, n, num + 1);
			int re1 = RMQ_rnk(L, x), re2 = RMQ_rnk(x, R);
			if (re2 > re1) {
				ans1[id] = re2; ans2[id] = sa[R];
			}
			else {
				int L_ = 1, R_ = L, re = L;
				while (L_ <= R_) {
					int mid = (L_ + R_) >> 1;
					if (RMQ_rnk(mid, x) >= re1) re = mid, R_ = mid - 1;
						else L_ = mid + 1;
				}
				int numb = T.get_sum(rt[l - 1], rt[r], 1, n, 1, re - 1);
				L = T.get_k(rt[l - 1], rt[r], 1, n, numb + 1);
				ans1[id] = re1; ans2[id] = sa[L];
			}
		}
	}
	
	for (int i = 1; i <= Q; i++) printf("%d %d\n", ans1[i], ans2[i]);
	
	return 0;
}

posted @ 2022-08-08 19:48  あおいSakura  阅读(48)  评论(0编辑  收藏  举报