首页 > 其他分享 >最长公共子串 题解

最长公共子串 题解

时间:2024-09-22 21:37:49浏览次数:1  
标签:子串 LCS .. int 题解 rep text 最长 rk

Statement

Q7.1.2.4,时限 4s

给一个串,定义 \(\mathrm{LCS}\) 为最长公共子串长度,\(q\) 次询问,每次给出 \(l,r\),求

\[ \operatorname{xor}_{i=1}^{r-l+1}\{i+\mathrm{LCS}(S[l,l+i-1],S[l+i-1,r])\} \]

\(n\le 10^5,q\le 30\)

Solution

tag:SA,线段树维护分治结构,orz hunction

题目就是让我们快速算 \(\text{LCS}(S[l,p],S[p,r]),p\in[l..r]\)

发现没法硬求,考虑到 \(p\) 每移一格 \(\text{LCS}\) 最多变化 \(1\)

假装已知 \(\text{LCS}\) 上界,列出一个 \(\text{LCS}\) 式子:\(\displaystyle\text{LCS}=\max_{i\in[L..p-\text{LCS}+1],j\in[p..R-\text{LCS}+1]}\{|\text{LCP}(i,j)|\}\)

令集合 \(\text{Left}=[L..p-\text{LCS}+1],\text{Right}=[p..R-\text{LCS}+1]\)

\[\text{LCS}=\max_{i\in\text{Left},j\in\text{Right}}\left\{\min_{k=\min(\text{rk}(i),\text{rk}(j))+1}^{\max(\text{rk}(i),\text{rk}(j))}\{\text{ht}(k)\}\right\}\\ \]

考虑:

  • \(\text{rk}(i)\) 作为 \(\text{ht}\) 区间的左端点时,应选择 \(\text{rk}(j)>\text{rk}(i)\) 中最小的 \(\text{rk}(j)\)
  • \(\text{rk}(j)\) 作为 \(\text{ht}\) 区间的左端点时,应选择 \(\text{rk}(i)>\text{rk}(j)\) 中最小的 \(\text{rk}(i)\)

考虑在 \(\text{rk}\) 值域上分治计算答案

设当前为 \([l,r]\),中点为 \(mid\),算跨过中点的贡献,那么设:

  • \([l..mid]\) 中来自 \(\text{Left}\) 集合的 \(\text{rk}\) 的最大值为 \(LLmx\)
  • \([l..mid]\) 中来自 \(\text{Right}\) 集合的 \(\text{rk}\) 的最大值为 \(LRmx\)
  • \([mid+1..r]\) 中来自 \(\text{Right}\) 集合的 \(\text{rk}\) 的最小值为 \(RRmn\)
  • \([mid+1..r]\) 中来自 \(\text{Left}\) 集合的 \(\text{rk}\) 的最小值为 \(RLmn\)

那么答案一定来自区间 \([LLmx+1..RRmn]\) 或 \([LRmx+1..RLmn]\)

因为还要求动态修改 \(\text{Left}\) 和 \(\text{Right}\),考虑线段树维护这个分治结构

那么删点、加点都可以轻松实现,就可以维护答案了

但是 \(\text{Left},\text{Right}\) 的定义是假装我们知道 \(\text{LCS}\) 的

\(\text{Left}=[L..p-\text{LCS}+1],\text{Right}=[p..R-\text{LCS}+1]\)

这时设上一个 \(\text{LCS}\) 为 \(\text{LCS}'\)

分别把 \(l=\text{LCS}'+1,\text{LCS}',\text{LCS}'-1\) 代入 \(\text{Left},\text{Right}\) 算一遍答案,记为 \(\text{Ans}\)

若 \(\text{Ans}\ge l\),那么 \(\text{LCS}=l\)

为什么是 \(\ge\) ?因为我拍了下,当 bababab,\(p=4\) 时代入式子算出的是 \(4\) 而不是 \(3\)

证明可以分类讨论

做完了!

#include <bits/stdc++.h>
using namespace std;
#define rep(i, j, k) for (int i = (j); i <= (k); ++i)
#define reo(i, j, k) for (int i = (j); i >= (k); --i)
typedef long long ll;
const int N = 4e5 + 10, LN = 20, INF = 2e9;
int n, q;
string s;

int m = 131, sa[N], rk[N], se[N], cnt[N], height[N];
void Rsort() {
	rep(i, 1, m) cnt[i] = 0;
	rep(i, 1, n) ++cnt[rk[i]];
	rep(i, 1, m) cnt[i] += cnt[i - 1];
	reo(i, n, 1) sa[cnt[rk[se[i]]]--] = se[i];
}
void SA() {
	if (n == 1) return (void)(sa[1] = rk[1] = 1);
	rep(i, 1, n) rk[i] = s[i - 1], se[i] = i;
	Rsort();
	for (int w = 1, p; w < n; w <<= 1, m = p) {
		p = 0;
		rep(i, n - w + 1, n) se[++p] = i;
		rep(i, 1, n) if (sa[i] > w) se[++p] = sa[i] - w;
		Rsort(), swap(rk, se), p = 0;
		rep(i, 1, n) 
			if (se[sa[i]] == se[sa[i - 1]] && se[sa[i] + w] == se[sa[i - 1] + w]) rk[sa[i]] = p;
			else rk[sa[i]] = ++p;
		if (p == n) break;
	}
}

int ln, Mn[N][LN];
void Getheight() {
	int k = 0;
	rep(i, 1, n) {
		if (k) --k;
		while (s[i + k - 1] == s[sa[rk[i] - 1] + k - 1]) ++k;
		height[rk[i]] = k;
	}
}
void initST() {
	ln = __lg(n);
	rep(i, 1, n) Mn[i][0] = height[i];
	rep(j, 1, ln)
		rep(i, 1, n - (1 << j) + 1)
			Mn[i][j] = min(Mn[i][j - 1], Mn[i + (1 << (j - 1))][j - 1]);
}
int LimitL, LimitR;
int AskMn(int l, int r) {
	if (!(2 <= l && l <= r && r <= n)) return 0;
	int k = __lg(r - l + 1);
	int ans = min(Mn[l][k], Mn[r - (1 << k) + 1][k]);
	return ans;
}

void init() {
	SA(), Getheight(), initST();
}

#define lc (u << 1)
#define rc ((u << 1) | 1)
#define mid ((l + r) >> 1)

struct Item {
	int LeftMx, LeftMn, RightMx, RightMn, ans;
	Item(int _LMx = -INF, int _LMn = INF, int _RMx = -INF, int _RMn = INF, int _ans = 0) {
		LeftMx = _LMx, LeftMn = _LMn, RightMx = _RMx, RightMn = _RMn, ans = _ans;
	}
	Item operator+ (const Item& u) const {
		int res = max({ans, u.ans, AskMn(LeftMx + 1, u.RightMn), AskMn(RightMx + 1, u.LeftMn)});
		return Item(max(LeftMx, u.LeftMx), min(LeftMn, u.LeftMn), max(RightMx, u.RightMx), min(RightMn, u.RightMn), res);
	}
} f[N << 2];
void up(int u) {
	f[u] = f[lc] + f[rc];
}
void build(int u, int l, int r) {
	f[u] = Item();
	if (l == r) return;
	build(lc, l, mid), build(rc, mid + 1, r);
}
void addLeft(int u, int l, int r, int x) {
	if (x < l || r < x) return;
	if (l == r) {
		f[u].LeftMn = min(f[u].LeftMn, x);
		f[u].LeftMx = max(f[u].LeftMx, x);
		if (f[u].RightMn != -INF) f[u].ans = 1;
		else f[u].ans = 0;
		return;
	}
	addLeft(lc, l, mid, x), addLeft(rc, mid + 1, r, x), up(u);
}
void addRight(int u, int l, int r, int x) {
	if (x < l || r < x) return;
	if (l == r) {
		f[u].RightMn = min(f[u].RightMn, x);
		f[u].RightMx = max(f[u].RightMx, x);
		if (f[u].LeftMn != -INF) f[u].ans = 1;
		else f[u].ans = 0;
		return;
	}
	addRight(lc, l, mid, x), addRight(rc, mid + 1, r, x), up(u);
}
void delRight(int u, int l, int r, int x) {
	if (x < l || r < x) return;
	if (l == r) return (void)(f[u].RightMn = INF, f[u].RightMx = -INF, f[u].ans = 0);
	delRight(lc, l, mid, x), delRight(rc, mid + 1, r, x), up(u);
}

#undef lc
#undef rc
#undef mid

void solve() {
	int L, R;
	cin >> L >> R;
	LimitR = R;
	int LCS = 1, ans = 2;
	build(1, 1, n);
	int LeftR = L, RightR = R;
	addLeft(1, 1, n, rk[L]);
	rep(k, L, R) addRight(1, 1, n, rk[k]);
	rep(p, L + 1, R) {
		LimitL = p;
		delRight(1, 1, n, rk[p - 1]);
		auto check = [&](int lcs) {
			int targetLeftR = p - lcs + 1, targetRightR = max(p, R - lcs + 1);
			while (LeftR < targetLeftR) addLeft(1, 1, n, rk[++LeftR]);
			while (RightR < targetRightR) addRight(1, 1, n, rk[++RightR]);
			while (RightR > targetRightR) delRight(1, 1, n, rk[RightR--]);
			return min({p - L + 1, R - p + 1, max(1, f[1].ans)}) >= lcs;
		};
		if (check(LCS + 1)) ++LCS;
		else if (check(LCS)) ;
		else if (check(LCS - 1)) --LCS;
		ans ^= p - L + 1 + LCS;
	}
	cout << ans << '\n';
}

int main() {
	ios::sync_with_stdio(false), cin.tie(nullptr);
	cin >> n >> q >> s, init();
	while (q--) solve();
	return 0;
}

标签:子串,LCS,..,int,题解,rep,text,最长,rk
From: https://www.cnblogs.com/laijinyi/p/18425923

相关文章

  • BZOJ 2555 = P5212 SubString 题解
    Statement给你一个字符串 \(\text{init}\),要求你支持两个操作:在当前字符串的后面插入一个字符串;询问字符串 \(s\) 在当前字符串中出现了几次?(作为连续子串)你必须在线支持这些操作。Solutionextend中link[cur]=q,相当于link,链加新建copy那里,相当于link,cut,链加询......
  • 2024 秋季模拟赛题解
    2024秋季模拟赛题解CSP-S模拟赛2024.9.8CSP-S模拟赛28T1签到题。对\(b\)分解质因数后便容易求解。T2考虑枚举\(\gcd(S)\)的取值\(x\),则\(\operatorname{lcm}(S)=m-x\)。那么同时变形\(\gcd\)和\(\operatorname{lcm}\)变为\(\gcd(S)=1,\operatorname{lcm}......
  • BZOJ 3277 串 题解
    Statement给 \(n\) 个串,问每个串有多少子串是所有 \(n\) 个串中至少 \(k\) 个串的子串。Solution1%%注意到\(\text{LCP}\)在后缀排序后一定是连续的一段,包含一个串的区间是连续的先预处理出对于所有左端点\(l\),最左的\(r\)满足\([l..r]\)中出现了至少\(k\)个......
  • BZOJ 4310 跳蚤 题解
    Statement把\(S\)分成不超过\(k\)段,使每段的最大子串中的最大串最小。输出这个串。Solution按排名二分这个串,check中从右往左贪心地划分,需要实现\(O(1)\)比较两个子串大小。#include<bits/stdc++.h>usingnamespacestd;#definerep(i,j,k)for(inti=(j);i<......
  • BZOJ 4545 DQS 的 trie 题解
    Statement维护一棵树,边权\(\in\{\texttta,\textttb,\textttc\}\),根为\(1\),定义这棵树的子串为从\(1\)走到所有点构成的字符串的所有后缀,需要支持以下操作:问当前树的本质不同子串数给一个点添加一棵子树问一个串在当前树中作为子串的出现次数Solution直接广义SAM+......
  • 力扣72-编辑距离(Java详细题解)
    题目链接:力扣72-编辑距离前情提要:因为本人最近都来刷dp类的题目所以该题就默认用dp方法来做。dp五部曲。1.确定dp数组和i下标的含义。2.确定递推公式。3.dp初始化。4.确定dp的遍历顺序。5.如果没有ac打印dp数组利于debug。每一个dp题目如果都用这五步分析清楚,那么......
  • ABC372 F 题解
    F-TeleportingTakahashi2先把问题转化一下:把环断开成链,复制\((K+1)\)层,每走一步就相当于前进一层:可以想到一个简单的dp:设\(f(i,j)\)表示走到第\(i\)层第\(j\)个位置的方案数。初始化:\(f(0,1)=1\),其它均为\(0\),表示Takahashi从第\(0\)层的\(1\)位置......
  • 【题解】「Public NOIP Round #2」找零
    【题解】「PublicNOIPRound#2」找零[官解](PublicNOIPRound#2题解-博客-Qingyu的博客(pjudge.ac))Tag:背包、dp凸优化决策点单调触碰到知识点盲区了,所以来写几笔。首先,由于我们只关心最终状态下\(1\)的最多个数,其实有用的面值只有\(5,1\)(其她的可以当成若干......
  • U389139 至少有一位重复的数字-题解
    题目传送门一、举例说明以\(654923\)为例要判断在\([0,654923]\)区间至少有一位重复的数值的数,可以考虑其补集,即\(\color{red}所有位数均不重复的数\),用\(N\)减去补集即为结果。首先可以将其分为两种情况。情况一,位数小于\(6\)位。所有位数均不重复的有\(9\time......
  • 【洛谷】P10417 [蓝桥杯 2023 国 A] 第 K 小的和 的题解
    【洛谷】P10417[蓝桥杯2023国A]第K小的和的题解题目传送门题解CSP-S1补全程序,致敬全A的答案,和神奇的预言家。写一下这篇的题解说不定能加CSP2024的RP代码#include<bits/stdc++.h>#definelowbit(x)x&(-x)#defineendl"\n"usingnamespacestd......