Y
K
N
U
F

P4094 [HEOI2016/TJOI2016] 字符串

link

非常好题目,使我的 SAM 旋转。

前置芝士:

  1. SAM 后缀自动机

  2. 线段树合并(动态开点与权值线段树)

形式化题意:

  给出字符串 $s$,每次询问 $a,b,c,d$。求 $s[a...b]$ 的所有子串和 $s[c...d]$ 的最长公共前缀的长度的最大值。

一个做法的乱讲:

注意到我们当然可以构造 SAM,但是暴力的比对和查找只会荣获一个 $n^3$的若智做法(和暴力没差)。注意到我们可以反过来看题目,干脆求出 $s[c...d]$ 在 $s[a...b]$ 中出现的最大长度,这样就完成了题意转化,我们从这个入手。

  我们假设现在已经有了 $s[c...e]$ (e ∈[c,d]) 在 $s[a...b]$ 中出现过,那么显然 $s[c...e]$ 的所有前缀都是合法的前缀,相同的我们也可以很容易得到若  $s[c...e]$ (e ∈[c,d]) 在 $s[a...b]$ 中没出现过,那么 $s[c...f]$ (f > e) 当然也不可能出现,于是我们可以二分答案。

  考虑如何 check,回忆起 SAM 中 $endpos$ 的定义,我们可以维护 $endpos (s[c...e])$,查询  $s[(a + e - c)...b]$ 的子串集合在 $endpos (s[c...e])$ 中是否有元素。至于维护,考虑在 SAM 上对代表这些子串的 id 建立权值线段树(因为只考虑“存在性”所以存布尔就行),用线段树合并的方式在总复杂度为 $O(n \log n)$  的时间内处理所有区间。直接将 $[a + e - c, b]$ 扔到 $endpos(s[c...e]) $上查就行。

   问题来到了如何找出代表着 $s[c...e]$ 的 SAM 上的 $id$,注意到在 parent tree 中,我们可以通过不断从 $s[1...e]$ 的位置往上跳来达到“遇见”$s[c...e]$ 的位置,至于判断遇见的是不是 $s[c...e]$,考虑在 parent tree 上单纯的从下往跑,$len$ 也下降,我们可以用这一点来判定。类似于求 lca,这一操作也可以用倍增优化。优化后达到优美的 $O( \log n)$ 的单次复杂度。

非常错误的复杂度分析:

  预处理 $n \log n$ + 询问 $n$* 二分 $\log n$ * (求 $p$ $\log n$ + 查询 $\log n$)  = $O(n \log ^2 n)$

代码
#include <bits/extc++.h>
#define e_ putchar_unlocked(' ')
#define en_ putchar_unlocked('\n')
using lint = long long;
using namespace std;
template <typename T> inline T in(T &n) {
	n = 0; char p = getchar_unlocked();
	while(p < '-') p = getchar_unlocked();
	// bool f = p == '-' ? (p = getchar_unlocked()) : 0;//
	do n = (n << 1) + (n << 3) + (p ^ 48), p = getchar_unlocked();
	while(isdigit(p));
	// return n = f ? -n : n;//
	return n;
}
template <typename T> inline void out(T x) {
	// if(x < 0) putchar_unlocked('-'), x = -x; //
	if(x >= 10) out(x / 10);
	putchar_unlocked(x % 10 + '0');
}
const int N = 1e5 + 10;

namespace DSU{
	bool w[N * 100];
	int ls[N * 100], rs[N * 100];
	int cntn;

	inline void up(int u) {
		w[u] = w[ls[u]] | w[rs[u]];
	}

	inline void update(int &u, int l, int r, int p) {
		if(!u) u = ++cntn;
		if(l == r) { w[u] = 1; return;}
		int m = (l + r) >> 1;
		if(m >= p) update(ls[u], l, m, p);
		else update(rs[u], m + 1, r, p);
		up(u); 
	}

	inline bool query(int u, int l, int r, int L, int R) {
		if(!u) return 0;
		if(L <= l and r <= R) return w[u];
		if(L > r || l > R) return 0;
		int m = (l + r) >> 1;
		return query(ls[u], l, m, L, R) | query(rs[u], m + 1, r, L, R);
	}

	inline int merge(int u, int v, int l, int r) {
		if(!u || !v) return u | v;
		int now = ++cntn;
		if(l == r) { w[now] = w[v] | w[u]; return now;}
		int m = (l + r) >> 1;
		ls[now] = merge(ls[u], ls[v], l, m);
		rs[now] = merge(rs[u], rs[v], m + 1, r);
		up(now); return now;
	}
}

using namespace DSU;

int rev[N], rt[N * 2];
int n, q;
string s;

namespace SAM {
	#define link LINK
	int ch[N * 2][26], link[N * 2], len[N * 2], tot = 1, las = 1;
	vector<int> e[N * 2];

	inline void add(int c) {
		int p = las, np = las = ++ tot;
		len[np] = len[p] + 1,
		update(rt[np], 1, n, len[np]);
		for(; p and !ch[p][c]; p = link[p]) ch[p][c] = np;
		if(!p) link[np] = 1;
		else {
			int q = ch[p][c];
			if(len[q] == len[p] + 1) link[np] = q;
			else {
				int nq = ++ tot;
				memcpy(ch[nq], ch[q], sizeof ch[0]);
				link[nq] = link[q];
				len[nq] = len[p] + 1;
				link[q] = link[np] = nq;
				for(; p and ch[p][c] == q; p = link[p]) ch[p][c] = nq;
			}
		}
	}

	int fa[N * 2][20];

	inline void dfs(int u) {
		for(int i = 1; i <= 18; i ++) 
			fa[u][i] = fa[fa[u][i - 1]][i - 1];
		for(int v : e[u]) {
			dfs(v);
			rt[u] = merge(rt[u], rt[v], 1, n);
		}
	}

	inline void init() {
		for(int i = 0; i < n; i ++) add(s[i] - 'a');
		for(int i = 2; i <= tot; i ++) fa[i][0] = link[i], e[link[i]].push_back(i);
		dfs(1);
	}
}

using namespace SAM;

inline bool check(int a, int b, int c, int mid) {
	int p = rev[c + mid - 1];
	for(int i = 18; i >= 0; i --) {
		if(fa[p][i] and len[fa[p][i]] >= mid) p = fa[p][i];
	}
	return query(rt[p], 1, n, a + mid - 1, b);
}

signed main() {
#ifndef ONLINE_JUDGE
	freopen("in.in", "r", stdin);
#endif
	in(n), in(q);
	cin >> s;
	init();
	for(int i = 0, p = 1; i < n; i ++) {
		p = ch[p][s[i] - 'a'],
		rev[i + 1] = p;
	}
	for(int i = 1, a, b, c, d; i <= q; i ++) {
		in(a), in(b), in(c), in(d);
		int l = 0, r = min(b - a + 1, d - c + 1), mid;
		while(l < r) {
			mid = (l + r + 1) >> 1;
			if(check(a, b, c, mid)) l = mid;
			else r = mid - 1;
		}
		out(r), en_;
	}
}

 

posted @ 2025-08-02 17:53  樓影沫瞬_Hz17  阅读(26)  评论(2)    收藏  举报