题解:P11823 [湖北省选模拟 2025] 最后的台词 / lines

简单题,但是深刻意识到了字符串的尽头是数据结构。

题意:给出一个串 \(S\),定义一个 \(k\) 连续序列 \(T\) 需要满足以下性质:

  • 所有串 \(T_i\) 都是 \(S\) 子串。
  • 相邻两个串 \(T_i, T_{i+1}\) 满足 \(T_i\) 长为 \(k\) 的后缀和 \(T_{i+1}\) 长为 \(k\) 的前缀相同。

现在询问 \(q\) 次,每次询问 \(T_1=S_{l_1\cdots r_1},T_{m}=S_{l_2,r_2}\)\(k\) 连续序列最小长度 \(m\) 是多少。

做法:

先特判答案为 \(1,2\) 的,可以直接 hash 计算。

首先先思考 \(k\) 是固定的怎么做,我们只在乎 \(k\) 长子串的事情,考虑把所有长度为 \(k\) 的子串拉出来,我们在每一个子串末尾的位置有一个点。那么我们发现我们可以以 \(0\) 的代价直接到任意一个当前串一样的。可以以 \(1\) 的代价直接到任意一个在当前串后面出现的串,发现在这种代价下,我们一定是往后跳到某个串然后跳到这个串出现第一次的位置,因为在前面一定比后面更优。那么我们记 \(f_i\) 代表 \(i\) 用代价 \(1\) 可以到的最小的位置。可以发现除了自环会形成一个森林的结构,直接倍增维护,从 \(r_1\) 往前跳即可,跳到标号 \(\le l_1+k-1\) 的位置。注意因为我还需要 \(T_1,T_m\) 还有一个衔接的串,所以答案应该是跳的次数 \(+3\)

然后考虑 \(k\) 不定怎么做,我们考虑让 \(k\) 从大往小扫,同时维护这个森林的结构。那么我们把 SAM 建出来,考虑 endpos 集合,当 \(k\rightarrow k-1\) 的时候,某些串会合并,我们发现,对于一个串出现在 \(a_1<a_2<\cdots <a_t\) de 位置时,我们在森林上 \([a_1,a_t]\) 的父亲会对 \(a_1\)\(\min\)。什么时候会合并呢?就是在 \(k\) 等于 SAM 某个节点的最长串长时就会合并,这个时候就要做一次更新父亲的操作。提前预处理这个 \(a_1,a_t\)

那么现在问题转化为,每次对区间取 \(\min\),询问从 \(x\) 开始跳 \(\le y\) 的次数。考虑用分块维护,散块暴力取 \(\min\),类似于弹飞绵羊处理,维护跳出块的步数。注意到区间取 \(\min\) 是对区间左端点,所以很好的是我们如果对一个整块打标记,那么我们一次跳跃一定会跳出去,可以比较方便计算跳一次的贡献。

回答询问就直接往块外跳,跳过了就暴力跳就行,复杂度 \(O(q\sqrt n)\),但是因为这个题还有一层字符串的原因所以其实常数特别小。

还有一个细节是,询问时我们可以以 \(0\) 代价跳到和 \(T_1\) 后缀一样的,\(T_m\) 类似可以到最后一个前缀一样的,这个需要在 SAM 的 endpos 树上跳就可以,具体可以见代码。

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e6 + 5, bs = 131;
int n, q;
string s;
unsigned long long h[maxn], pw[maxn];
unsigned long long query(int l, int r) {
	return (h[r] - h[l - 1] * pw[r - l + 1]);
}
struct node {
	int nxt[26], lnk, len, l, r, ed; 
	node() {
		l = 2e9, r = 0;
	}
} ;
struct SAM {
	node tr[maxn]; int tot = 1, lst = 1;
	int nw[maxn], f[22][maxn];
	inline void add(int c, int pos) {
		int cur = ++tot, p = lst;
		tr[cur].len = tr[p].len + 1;
		tr[cur].l = tr[cur].r = pos;
		while(p && !tr[p].nxt[c])
			tr[p].nxt[c] = cur, p = tr[p].lnk;
		if(!p)
			tr[cur].lnk = 1;
		else {
			int q = tr[p].nxt[c];
			if(tr[p].len + 1 == tr[q].len)
				tr[cur].lnk = q;
			else {
				int clone = ++tot;
				tr[clone] = tr[q]; tr[clone].len = tr[p].len + 1;
				tr[clone].l = 2e9, tr[clone].r = 0;
				while(p && tr[p].nxt[c] == q)
					tr[p].nxt[c] = clone, p = tr[p].lnk;
				tr[q].lnk = tr[cur].lnk = clone;
			}
		}
		nw[pos] = cur;
	//	cout << p << " " << tr[cur].lnk << " " << cur << endl;
		lst = cur;
	}	
	vector<int> p[maxn];
	vector<int> e[maxn];
	void prepare1() {
		for (int i = 1; i <= tot; i++)
			e[tr[i].lnk].push_back(i), f[0][i] = tr[i].lnk;
		for (int j = 1; j <= 21; j++)
			for (int i = 1; i <= tot; i++)
				f[j][i] = f[j - 1][f[j - 1][i]];
	}
	void dfs(int u) {
		tr[u].ed = tr[u].len;
		for (int i = 0; i < e[u].size(); i++) {
			int v = e[u][i];
			dfs(v);
			tr[u].l = min(tr[u].l, tr[v].l);
			tr[u].r = max(tr[u].r, tr[v].r);
		}
	//	cout << u << " " << tr[u].len << " " << tr[u].ed << " " << tr[u].l << "ASD " << tr[u].r << " " << tr[tr[u].lnk].len << endl;
	}
	void prepare() {
		for (int i = 2; i <= tot; i++)
			p[tr[i].ed].push_back(i);
	}
	inline int get_pos(int p, int l) {
		if(tr[tr[nw[p]].lnk].len < l)
			return nw[p];
		for (int i = 21; i >= 0; i--)
			if(tr[f[i][nw[p]]].len >= l)
				nw[p] = f[i][nw[p]];
		return nw[p];
	}
} tree;
int l1[maxn], r1[maxn], l2[maxn], r2[maxn], ans[maxn];
vector<int> qry[maxn];
const int inf = 2e9;
struct Block {
	int pre[maxn], a[maxn], l[maxn], r[maxn], pos[maxn], tag[maxn], B = 400, dis[maxn];
	void build_block(int p) {
		if(tag[p] < inf) 
			return ;
		for (int i = l[p]; i <= r[p]; i++) {
			if(a[i] == i)
				pre[i] = i, dis[i] = 0;
			else if(a[i] < l[p])
				pre[i] = a[i], dis[i] = 1;
			else
				pre[i] = pre[a[i]], dis[i] = dis[a[i]] + 1;
		}
	}
	void prepare() {
		for (int i = 1; i <= n; i++) {
			pos[i] = (i - 1) / B + 1;
			if(!l[pos[i]])
				l[pos[i]] = i;
			r[pos[i]] = i;
			a[i] = i;
		}
		for (int i = 1; i <= pos[n]; i++)
			tag[i] = inf, build_block(i);
	}
	inline void change(int lx, int rx, int v) {
		if(pos[lx] == pos[rx]) {
			if(tag[pos[lx]] < lx)
				return ;
			for (int i = lx; i <= rx; i++)
				a[i] = min(a[i], v);
			build_block(pos[lx]);
		}
		else {
			for (int i = lx; i <= r[pos[lx]]; i++)
				a[i] = min(a[i], v);
			for (int i = l[pos[rx]]; i <= rx; i++)
				a[i] = min(a[i], v);
			for (int i = pos[lx] + 1; i <= pos[rx] - 1; i++)
				tag[i] = min(tag[i], v);
			build_block(pos[lx]), build_block(pos[rx]);
		}
	}
	inline int get_nxt(int x) {
		return min(tag[pos[x]], pre[x]);
	}
	inline int query(int x, int y) {
		if(x >= y)
			return 0;
		int res = 0;
		while(1) {
			if(tag[pos[y]] != inf) {
				int to = min(tag[pos[y]], a[y]);
				y = to, res++;
			}
			else if(pre[y] >= x && (pre[y] != y))
				res += dis[y], y = pre[y];
			else {
				while(1) {
					int to = min(tag[pos[y]], a[y]);
					if(y == to)
						return -4;
					y = to; res++;
			//		cout << x << " " << y << endl;
					if(x >= y)
						break;
				}
				break;
			}
			if(y <= x)
				return res;
		//	cout << x << " " << y << " " << p << endl;
		}
		return res;
	}
} B;
int read() {
	int sum = 0; char c = getchar();
	while(!isdigit(c))
		c = getchar();
	while(isdigit(c))
		sum = sum * 10 + c - '0', c = getchar();
	return sum;
}
void write(int x) {
	if(x < 0) {
		putchar('-');
		x = -x;
	}
	if(x <= 9) {
		putchar(x + '0');
		return ;
	}
	write(x / 10);
	putchar(x % 10 + '0');
}
signed main() {
//	freopen("test.in", "r", stdin);
//	freopen("std.out", "w", stdout);
	cin >> s, q = read(), n = s.size(), s = ' ' + s;
	pw[0] = 1;
//	cout << q << endl;
	for (int i = 1; i <= n; i++)
		pw[i] = pw[i - 1] * bs, h[i] = h[i - 1] * bs + s[i] - 'a' + 1;
	for (int i = 1; i <= n; i++)
		tree.add(s[i] - 'a', i);
	tree.prepare1();
	tree.dfs(1), tree.prepare();
	for (int i = 1; i <= q; i++) {
		int k;
		l1[i] = read(), r1[i] = read(), l2[i] = read(), r2[i] = read(), k = read();
		//cout << query(l1[i], r1[i]) << " " << query(l2[i], r2[i]) << endl;
		if(query(l1[i], r1[i]) == query(l2[i], r2[i]))
			ans[i] = 1;
		else if(query(r1[i] - k + 1, r1[i]) == query(l2[i], l2[i] + k - 1))
			ans[i] = 2;
		else
			qry[k].push_back(i);
	}
	B.prepare();
	for (int i = n; i >= 1; i--) {
		for (int j = 0; j < tree.p[i].size(); j++) {
			B.change(tree.tr[tree.p[i][j]].l, tree.tr[tree.p[i][j]].r, tree.tr[tree.p[i][j]].l);
		}
	//	cout << i << endl;
		for (int j = 0; j < qry[i].size(); j++) {
			int bg = tree.get_pos(r1[qry[i][j]], i); bg = tree.tr[bg].l;
			int ed = tree.get_pos(l2[qry[i][j]] + i - 1, i); ed = tree.tr[ed].r;
			ans[qry[i][j]] = B.query(ed, bg) + 3;
		}
	//	cout << i << endl;
	}
	for (int i = 1; i <= q; i++)
		write(ans[i]), putchar('\n');
	return 0;
}
/*
aba
0
*/
posted @ 2025-11-13 10:28  LUlululu1616  阅读(6)  评论(0)    收藏  举报