首页 > 其他分享 >[BZOJ3230] 相似子串 题解

[BZOJ3230] 相似子串 题解

时间:2024-12-30 17:30:19浏览次数:1  
标签:子串 int 题解 s1 后缀 BZOJ3230 s2 rk

\(\text{[BZOJ3230] 相似子串 题解}\)

巧妙地利用了后缀数组的一些奇妙性质。

先考虑第一问。首先去处理本质不同的子串这个东西。这个东西我们显然是见过的,于是套路地建出 SA 求出 \(\operatorname{height}\) 数组。每一个子串对应的都是一个后缀的前缀。由于串都是本质不同的,那么两个串 \((l_1,r_1),(l_2,r_2)\) 的 \(\operatorname{lcp}\) 也就是 \(\operatorname{lcp(s_{l_1},s_{l_2})}\),其中 \(\operatorname{s_i}\) 表示的是 \(s(i\sim n)\)。这样便是容易求的。

于是现在我们需要知道每个本质不同的串的编号和字符串内下标的对应关系。然后发现题目让我们给这些串排序。那么求出后缀数组的过程实际上也就给每个后缀排了序,而且容易发现的是对于后缀 \(s(i)\) 的每个前缀,他们在排序后的下标同样是连续的。这样我们可以求出每个子串的下标,但是需要留意的是我们要去重。那这个显然是容易的,对于 \(i\) 到 \(i+1\) 的转移,减去 \(\operatorname{height}_{i+1}\) 即可。

这样一来容易知道的是每个后缀的第一个前缀的排名,于是第一问可以二分去做。对于第二问,由于此时我们已经知道了串的左端点,那么右端点同样是容易得出的。于是反着建 SA 跑一遍即可。

注意开 long long

代码:

#include <bits/stdc++.h>
#define N 200005
#define M 21
#define ll long long
using namespace std;
int n, q;
struct _SA {
	string s;
	int sa[N], rk[N], lk[N], id[N], cnt[N];
	void SA() {
		int m = 128, p = 0;
		for (int i = 1; i <= n; i++) cnt[rk[i] = s[i]]++;
		for (int i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
		for (int i = n; i >= 1; i--) sa[cnt[rk[i]]--] = i;
		for (int w = 1;; w <<= 1, m = p) {
			int cur = 0;
			for (int i = n - w + 1; i <= n; i++) id[++cur] = i;
			for (int i = 1; i <= n; i++)
				if (sa[i] > w) id[++cur] = sa[i] - w;
			for (int i = 1; i <= m; i++) cnt[i] = 0;
			for (int i = 1; i <= n; i++) cnt[rk[i]]++;
			for (int i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
			for (int i = n; i >= 1; i--) sa[cnt[rk[id[i]]]--] = id[i];
			swap(lk, rk);
			p = 0;
			for (int i = 1; i <= n; i++) {
				if (lk[sa[i]] == lk[sa[i - 1]] && lk[sa[i] + w] == lk[sa[i - 1] + w]) rk[sa[i]] = p;
				else rk[sa[i]] = ++p;
			}
			if (p == n) break;
		}
	}
	int h[N];
	void H() {
		for (int i = 1, k = 0; i <= n; i++) {
			if (k) --k;
			while (s[i + k] == s[sa[rk[i] - 1] + k]) ++k;
			h[rk[i]] = k;
		}
	}
	int st[N][M];
	void init() {
		SA();
		H();
		for (int i = 1; i <= n; i++) st[i][0] = h[i];
		for (int j = 1; j < M; j++)
			for (int i = 1; i <= n - (1 << j) + 1; i++)
				st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
	}
	int query(int l, int r) {
		int k = log2(r - l + 1);
		return min(st[l][k], st[r - (1 << k) + 1][k]);
	}
} a, b;
ll srt[N], ed[N];

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> q;
	cin >> a.s;
	b.s = a.s;
	reverse(b.s.begin(), b.s.end());
	a.s = " " + a.s, b.s = " " + b.s;
	a.init(), b.init();
	srt[1] = 1, ed[1] = n - a.sa[1] + 1;
	for (int i = 2; i <= n; i++) 
		srt[i] = ed[i - 1] + 1, ed[i] = srt[i] + n - a.sa[i] - a.h[i];
	srt[n + 1] = 1e9;
	ll mx = ed[n];
	while (q--) {
		ll x, y;
		cin >> x >> y;
		if (y > mx)  {
			cout << "-1\n";
			continue;
		}
		int s1 = upper_bound(srt + 1, srt + n + 2, x) - srt - 1, s2 = upper_bound(srt + 1, srt + n + 2, y) - srt - 1;
		int l1 = a.sa[s1], l2 = a.sa[s2];
		int r1 = l1 + x - srt[s1] + a.h[s1], r2 = l2 + y - srt[s2] + a.h[s2];
		int A = 0, B = 0;
		if (s1 > s2) swap(s1, s2);
		if (s1 == s2) A = min(r1 - l1 + 1, r2 - l2 + 1);
		else A = a.query(s1 + 1, s2);
		s1 = b.rk[n - r1 + 1], s2 = b.rk[n - r2 + 1];
		if (s1 > s2) swap(s1, s2);
		if (s1 == s2) B = min(r1 - l1 + 1, r2 - l2 + 1);
		else B = b.query(s1 + 1, s2);
		A = min({A, r1 - l1 + 1, r2 - l2 + 1}), B = min({B, r1 - l1 + 1, r2 - l2 + 1});
		cout << (long long)A * A + (long long)B * B << '\n';
	}
	return 0;
}

标签:子串,int,题解,s1,后缀,BZOJ3230,s2,rk
From: https://www.cnblogs.com/Rock-N-Roll/p/18641810

相关文章

  • CF2053F Earnest Matrix Complement 题解
    我也不知道显不显然,有一个重要性质是:一定存在一种最优方案,使得每一行的\(-1\)填的都是同一个数。证明的话直接调整即可,假设现在我们有一个最优方案,并且第\(i\)行填着不同的数,我们将每一种颜色\(u\),按\(c_{u,i-1}+c_{u,i+1}\)排个序,意思就是每多一个颜色\(u\)都会加上......
  • DataGrip 2024.3安装详细教程与激活方法(附常见问题解决)
    DataGrip概述DataGrip是一款功能强大的,适用于关系数据库和NoSQL数据库的强大跨平台工具温馨提示:本文中的方法仅供学习交流使用,如果条件允许,请支持正版软件。删除旧版本DataGrip如果您的电脑中已经安装了旧版本的DataGrip,建议首先将其完全卸载。操作步骤如下(如果未安装......
  • PyInstaller打包exe提示文件缺失,无法找到文件/文件夹路径的问题解析(为什么PyInstaller
    文章目录......
  • AT_abc237_g [ABC237G] Range Sort Query 题解
    题目传送门前置知识珂朵莉树/颜色段均摊解法观察到只有\(=x\)的位置才是重要的,而其他位置上的数具体是什么并不重要,我们只需要关注其大小关系。第一遍将\(\gex\)的数看做\(1\),将\(<x\)的数看做\(0\)。第二遍将\(>x\)的数看做\(1\),将\(\lex\)的数看做\(1\)。......
  • 题解:P11487 「Cfz Round 5」Gnirts 10
    枚举\(S\)每个前缀,计算其对答案的贡献。把枚举出来的前缀\(S[1\dotsi]\)看成已有的串,我们要插入一些字符使得它有\(n\)个\(0\),\(m\)个\(1\),且\(S[1\dotsi]\)为\(T\)的子序列,而\(S[1\dotsi+1]\)不是。问方案数(统计到最后答案的时候要乘长度)。发现在\(1\)前......
  • P6272 [湖北省队互测2014] 没有人的算术 题解
    本文参考了湖北省队互测Week1解题报告,在部分之处说明可能不如原题解,如有错误请指出。洛谷上的题面缺失了特殊性质,不过原题的特殊性质还是比较具有启发性的,下面是原题面中的数据范围测试点\(1\)考察选手的读题能力。按照题目提供的比较方式暴力递归即可。测试点\(2\sim......
  • LeetCode1.两数求和 C题解(简单)
    两数求和1.原题目题目示例2.思路解析3.具体操作1.原题目题目LeetCode题库的第1题题目为:给定一个整数数组nums和一个整数目标值target,请你在该数组中找出和为目标值target的那两个整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案,并且你不能使用两......
  • [算法/数据结构]系列 华为面试原题:和为n的子串(前缀和+哈希表)
    [算法/数据结构]系列华为面试原题:和为n的子串(前缀和+哈希表)文章目录[算法/数据结构]系列华为面试原题:和为n的子串(前缀和+哈希表)面试原题样例分析代码及思路面试原题输入一串只有0和1的数组,返回输入和为n的子串的个数。样例:输入:[011100],n=3输出:6样......
  • [CF2053C] Bewitching Stargazer 题解
    我们不妨直接递归模拟算答案。定义\(f(l,r)\)表示左右端点为\(l,r\)的答案。记\(mid\gets\lfloor\frac{l+r}{2}\rfloor\),于是:\[f(l,r)=\begin{cases}f(l,mid)+f(mid+1,r)&(r-l+1)\equiv0\pmod2\\f(l,mid-1)+f(mid+1,r)+mid&{\text{otherwi......
  • [CF2043C] Sums on Segments 题解
    我们先想全是\(\pm1\)的。令区间内最小子段和为\(mn\),最大子段和为\(mx\),注意到\([mn,mx]\)内的数全都能被凑出来。证明:我们在区间\([l,r]\)内任意取一个子区间\([l',r']\)。定义【扩展】为将一个区间左边或右边添加一个数。定义【收缩】为将一个区间左边或右边去......