[BZOJ4556][Tjoi2016&Heoi2016]字符串(二分+后缀数组+主席树)

Address

洛谷P4094
BZOJ4556
LOJ#2059

Solution

先二分答案,转化成判定性问题:
s [ a . . . b ] s[a...b] s[a...b] 是否存在一个子串和 s [ c . . . d ] s[c...d] s[c...d] 的 lcp 大于等于 m i d mid mid
等价地,判断是否存在一个 i ∈ [ a , b − m i d + 1 ] i\in[a,b-mid+1] i[a,bmid+1] 满足以 i i i 为开头的后缀和以 c c c 开头的后缀的 lcp 大于等于 m i d mid mid
s s s 的后缀数组构建出来之后, lcp 可以转化成 h e i g h t height height 区间最小值。
所以不管 r a n k [ i ] &lt; r a n k [ c ] rank[i]&lt;rank[c] rank[i]<rank[c] 还是 r a n k [ i ] ≥ r a n k [ c ] rank[i]\ge rank[c] rank[i]rank[c]
我们都要让 r a n k [ i ] rank[i] rank[i] r a n k [ c ] rank[c] rank[c] 尽可能接近,这样才能使区间的最小值最大。
问题转化成:
(1)求一个 i ∈ [ a , b − m i d + 1 ] , r a n k [ i ] ≤ r a n k [ c ] i\in[a,b-mid+1],rank[i]\le rank[c] i[a,bmid+1],rank[i]rank[c] 使得 r a n k [ i ] rank[i] rank[i] 最大。
(2)求一个 i ∈ [ a , b − m i d + 1 ] , r a n k [ i ] ≥ r a n k [ c ] i\in[a,b-mid+1],rank[i]\ge rank[c] i[a,bmid+1],rank[i]rank[c] 使得 r a n k [ i ] rank[i] rank[i] 最小。
也就是求区间内某个数的前驱后继。
可以以原串中的位置为下标, r a n k rank rank 为权值建立主席树,通过查询 区间排名 / 区间内排名为 k k k 的数 实现求前驱后继。
复杂度 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define Pow(k, n) for (k = 1; k < n; k <<= 1, swap(x, y))
using namespace std;
inline int read() {
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}
const int N = 1e5 + 5, M = 3e6 + 5, LogN = 20;
int n, m, sa[N], rank[N], height[N], w[N], rt[N], QAQ, RMQ[N][LogN], Log[N];
char s[N];
struct cyx {
	int lc, rc, sum;
} T[M];
void ins(int y, int &x, int l, int r, int p) {
	T[x = ++QAQ] = T[y]; T[x].sum++;
	if (l == r) return;
	int mid = l + r >> 1;
	if (p <= mid) ins(T[y].lc, T[x].lc, l, mid, p);
	else ins(T[y].rc, T[x].rc, mid + 1, r, p);
}
int rankl(int y, int x, int l, int r, int p) {
	if (l == r) return T[x].sum - T[y].sum;
	int mid = l + r >> 1;
	if (p <= mid) return rankl(T[y].lc, T[x].lc, l, mid, p);
	else return rankl(T[y].rc, T[x].rc, mid + 1, r, p)
		+ T[T[x].lc].sum - T[T[y].lc].sum;
}
int rankr(int y, int x, int l, int r, int p) {
	if (l == r) return T[x].sum - T[y].sum;
	int mid = l + r >> 1;
	if (p > mid) return rankr(T[y].rc, T[x].rc, mid + 1, r, p);
	else return rankr(T[y].lc, T[x].lc, l, mid, p)
		+ T[T[x].rc].sum - T[T[y].rc].sum;
}
int getrankl(int y, int x, int l, int r, int p) {
	if (l == r) return l;
	int delta = T[T[x].lc].sum - T[T[y].lc].sum, mid = l + r >> 1;
	if (p <= delta) return getrankl(T[y].lc, T[x].lc, l, mid, p);
	else return getrankl(T[y].rc, T[x].rc, mid + 1, r, p - delta);
}
int getrankr(int y, int x, int l, int r, int p) {
	if (l == r) return l;
	int delta = T[T[x].rc].sum - T[T[y].rc].sum, mid = l + r >> 1;
	if (p <= delta) return getrankr(T[y].rc, T[x].rc, mid + 1, r, p);
	else return getrankr(T[y].lc, T[x].lc, l, mid, p - delta);
}
int pre(int l, int r, int p) {
	int rk = rankl(rt[l - 1], rt[r], 1, n, p);
	if (!rk) return 0;
	return getrankl(rt[l - 1], rt[r], 1, n, rk);
}
int suf(int l, int r, int p) {
	int rk = rankr(rt[l - 1], rt[r], 1, n, p);
	if (!rk) return 0;
	return getrankr(rt[l - 1], rt[r], 1, n, rk);
}
void cyxisdalao() {
	int i, j, k, m = 26, *x = rank, *y = height;
	For (i, 1, n) w[x[i] = s[i] - 'a' + 1]++;
	For (i, 2, m) w[i] += w[i - 1];
	For (i, 1, n) sa[w[x[i]]--] = i;
	Pow(k, n) {
		int tt = 0;
		For (i, n - k + 1, n) y[++tt] = i;
		For (i, 1, n) if (sa[i] > k) y[++tt] = sa[i] - k;
		memset(w, 0, sizeof(w));
		For (i, 1, n) w[x[i]]++;
		For (i, 2, m) w[i] += w[i - 1];
		Rof (i, n, 1) sa[w[x[y[i]]]--] = y[i];
		m = 0;
		For (i, 1, n) {
			int u = sa[i], v = sa[i - 1];
			y[u] = x[u] != x[v] || x[u + k] != x[v + k] ? ++m : m;
		}
		if (m == n) break;
	}
	if (y != rank) copy(y, y + n + 1, rank);
	k = height[1] = 0;
	For (i, 1, n) {
		if (k) k--;
		int x = sa[rank[i] - 1];
		while (s[x + k] == s[i + k]) k++;
		height[rank[i]] = k;
	}
	Log[0] = -1;
	For (i, 1, n) Log[i] = Log[i >> 1] + 1;
	For (i, 2, n) RMQ[i][0] = height[i];
	For (j, 1, 17) For (i, 2, n - (1 << j) + 1)
		RMQ[i][j] = min(RMQ[i][j - 1], RMQ[i + (1 << j - 1)][j - 1]);
}
int LCP(int x, int y) {
	if (x == y) return n - x + 1;
	x = rank[x]; y = rank[y];
	if (x > y) swap(x, y); x++;
	int k = Log[y - x + 1];
	return min(RMQ[x][k], RMQ[y - (1 << k) + 1][k]);
}
int main() {
	int i, a, b, c, d;
	n = read(); m = read();
	scanf("%s", s + 1);
	cyxisdalao();
	For (i, 1, n) ins(rt[i - 1], rt[i], 1, n, rank[i]);
	while (m--) {
		a = read(); b = read(); c = read(); d = read();
		int l = 1, r = min(b - a + 1, d - c + 1);
		while (l <= r) {
			int mid = l + r >> 1, p = pre(a, b - mid + 1, rank[c]),
				s = suf(a, b - mid + 1, rank[c]), rp = 0;
			if (p) rp = max(rp, LCP(sa[p], c));
			if (s) rp = max(rp, LCP(sa[s], c));
			if (rp >= mid) l = mid + 1;
			else r = mid - 1;
		}
		printf("%d\n", r);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值