题目链接

https://codeforces.com/contest/1063/problem/F

题解

虽然本题有时间复杂度较高但非常好写的做法......

首先,若答案为 \(k\),则一定存在一种最优方案,使得对于任意 \(i \in [1, k)\),有 \(|t_i| = |t_{i + 1}| + 1\),换句话说,第 \(i + 1\) 个字符串可以通过第 \(i\) 个字符串删掉首字符或尾字符得到。其正确性显然。

这样,我们可以记录 \(f_i\) 表示串 \(s\) 从 \(i\) 开始的的后缀,选择一个前缀作为串 \(t_1\) 可得到的最大的 \(k\)。求 \(f_i\) 显然可以二分,设二分的值为 \(v\),由于最终得到的相邻两个字符串的长度差为 \(1\),因此二分的值 \(v\) 即为当前以 \(s_i\) 开头的字符串 \(t_1\) 的长度。我们的任务是判断能否找到一个以 \(s_{i \sim i + v - 1}\) 删去首/尾字符作为前缀的后缀,使得该后缀对应的位置 \(p\) 满足 \(f_p \geq v - 1\)。

直接求难以入手,我们将问题转化一下,我们希望找到所有和从 \(i\) 或 \(i + 1\) 开始的后缀的 lcp 长度不小于 \(v - 1\) 的后缀对应位置的 \(f\) 的最大值。我们求出原串 \(s\) 的后缀数组后,合法的后缀对应的 \(\rm rank\) 一定是一段连续的且包含 \({\rm rank}_i\) 或 \({\rm rank}_{i + 1}\) 的区间。这样,我们就可以用线段树维护 \(f\):把位置 \(p\) 对应的 \(f\) 值记录到线段树的 \({\rm rank}_p\) 位置,查询时直接区间查询最大值即可。合法后缀的 \(\rm rank\) 对应的区间也能通过二分得到,因此单次判断的时间复杂度为 \(O(\log n)\),整个问题的时间复杂度即为 \(O(n \log^2 n)\)。

不过这并不是最优的时间复杂度。

首先给出结论:\(f_i \leq f_{i + 1} + 1\)。其正确性显然:由于 \(f_{i + 1}\) 较 \(f_i\) 而言,存在一种方案,第一个字符串只少了 \(s_i\) 一个字符,这样最多只会少一个字符串,故 \(f_{i + 1} \geq f_i - 1\),即 \(f_i \leq f_{i + 1} + 1\)。

有了该结论后,我们就可以省去二分,对于位置 \(i\) 直接从 \(f_{i + 1} + 1\) 开始暴力判断,直到遇到第一个合法的值停止,将其作为 \(f_i\) 即可。这样,总时间复杂度就优化至了 \(O(n \log n)\)。

然而,上述做法始终回避了一个问题,那就是上述做法并没有使得字符串与字符串间不存在交。不过,使用 \(O(n \log n)\) 的做法能够很好地解决这一问题。注意到我们在判断位置 \(f_p = v\) 是否合法时,为了保证不存在交,我们希望只能查询到位置 \(p + v\) 及其之后的 \(f\) 值。由于当 \(p\) 固定时,我们枚举的 \(v\) 是从大到小的,又由于 \(f_p - f_{p + 1} \leq 1\),因此我们希望查询到的合法区域的左端点也是单调不上升的。我们只需记录一个指针即可,时间复杂度依然是 \(O(n \log n)\)。

代码

#include<bits/stdc++.h>

using namespace std;

#define rg register

template<typename T> inline bool checkMax(T& a, const T& b) {
return a < b ? a = b, true : false;
} const int N = 5e5 + 10; int n, c[N], x[N], y[N], sa[N], _rank[N], height[N], f[N][20], logv[N], g[N];
char s[N]; inline void get_sa(int m) {
for (rg int i = 0; i < n; ++i) {
++c[x[i] = s[i]];
}
for (rg int i = 1; i < m; ++i) {
c[i] += c[i - 1];
}
for (rg int i = n - 1; ~i; --i) {
sa[--c[x[i]]] = i;
}
for (rg int k = 1; k <= n; k <<= 1) {
int p = 0;
for (rg int i = n - 1; i >= n - k; --i) {
y[p++] = i;
}
for (rg int i = 0; i < n; ++i) {
if (sa[i] >= k) {
y[p++] = sa[i] - k;
}
}
fill(c, c + m, 0);
for (rg int i = 0; i < n; ++i) {
++c[x[y[i]]];
}
for (rg int i = 1; i < m; ++i) {
c[i] += c[i - 1];
}
for (rg int i = n - 1; ~i; --i) {
sa[--c[x[y[i]]]] = y[i];
}
swap(x, y);
p = 1, x[sa[0]] = 0;
for (rg int i = 1; i < n; ++i) {
x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? p - 1 : p++;
}
if (p >= n) {
break;
}
m = p;
}
} inline void get_height() {
for (rg int i = 0; i < n; ++i) {
_rank[sa[i]] = i;
}
int k = 0;
for (rg int i = 0; i < n; ++i) {
k -= k ? 1 : 0;
if (!_rank[i]) {
continue;
}
int j = sa[_rank[i] - 1];
for (; s[i + k] == s[j + k]; ++k);
height[_rank[i]] = k;
}
for (rg int i = 0; i < n; ++i) {
f[i][0] = height[i];
}
for (rg int i = 2; i <= n; ++i) {
logv[i] = logv[i >> 1] + 1;
}
for (rg int j = 1; (1 << j) <= n; ++j) {
for (rg int i = 0; i + (1 << j) - 1 < n; ++i) {
f[i][j] = min(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
}
} inline int query(int x, int y) {
x = _rank[x];
y = _rank[y];
if (x > y) {
swap(x, y);
}
++x;
int k = logv[y - x + 1];
return min(f[x][k], f[y - (1 << k) + 1][k]);
} #define lo (o<<1)
#define ro (o<<1|1) int maxv[N << 2]; inline void modify(int l, int r, int o, int p, int v) {
if (l == r) {
maxv[o] = v;
} else {
int mid = l + r >> 1;
(p <= mid) ? modify(l, mid, lo, p, v) : modify(mid + 1, r, ro, p, v);
maxv[o] = max(maxv[lo], maxv[ro]);
}
} inline int query(int l, int r, int o, int ql, int qr) {
if (ql <= l && r <= qr) {
return maxv[o];
} else {
int mid = l + r >> 1, res = 0;
if (ql <= mid) {
checkMax(res, query(l, mid, lo, ql, qr));
} if (qr > mid) {
checkMax(res, query(mid + 1, r, ro, ql, qr));
}
return res;
}
} inline bool binary_search(int p, int len) {
int l = 0, r = _rank[p], ql, qr;
while (l ^ r) {
int mid = l + r >> 1;
if (query(sa[mid], p) >= len) {
r = mid;
} else {
l = mid + 1;
}
}
ql = l;
l = _rank[p], r = n - 1;
while (l ^ r) {
int mid = (l + r >> 1) + 1;
if (query(sa[mid], p) >= len) {
l = mid;
} else {
r = mid - 1;
}
}
qr = l;
return query(0, n - 1, 1, ql, qr) >= len;
} inline bool check(int p, int len) {
--len;
return binary_search(p, len) | binary_search(p + 1, len);
} int main() {
scanf("%d%s", &n, s);
get_sa('z' + 1);
get_height();
int ans = 0;
for (rg int i = n - 1, p = n - 1, last_val = 0; ~i; --i) {
for (++last_val; !check(i, last_val); --last_val, modify(0, n - 1, 1, _rank[p], g[p]), --p);
checkMax(ans, g[i] = last_val);
}
printf("%d\n", ans);
return 0;
}

CF1063F. String Journey(后缀数组+线段树)的更多相关文章

  1. [CF1063F]String Journey[后缀数组+线段树]

    题意 在 \(S\) 中找出 \(t\) 个子串满足 \(t_{i+1}\) 是 \(t_{i}\) 的子串,要让 \(t\) 最大. \(|S| \leq 5\times 10^5\). 分析 定义 ...

  2. Codeforces 1063F - String Journey(后缀数组+线段树+dp)

    Codeforces 题面传送门 & 洛谷题面传送门 神仙题,做了我整整 2.5h,写篇题解纪念下逝去的中午 后排膜拜 1 年前就独立切掉此题的 ymx,我在 2021 年的第 5270 个小 ...

  3. BZOJ 1396: 识别子串( 后缀数组 + 线段树 )

    这道题各位大神好像都是用后缀自动机做的?.....蒟蒻就秀秀智商写一写后缀数组解法..... 求出Height数组后, 我们枚举每一位当做子串的开头. 如上图(x, y是height值), Heigh ...

  4. 【XSY1551】往事 广义后缀数组 线段树合并

    题目大意 给你一颗trie树,令\(s_i\)为点\(i\)到根的路径上的字符组成的字符串.求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\) \(LCP=\) ...

  5. Luogu4770 NOI2018你的名字(后缀数组+线段树)

    即求b串有多少个本质不同的非空子串,在a串的给定区间内未出现.即使已经8102年并且马上就9102年了,还是要高举SA伟大旗帜不动摇. 考虑离线,将所有询问串及一开始给的串加分隔符连起来,求出SA.对 ...

  6. BZOJ 2865 字符串识别 | 后缀数组 线段树

    集训讲字符串的时候我唯一想出正解的题-- 链接 BZOJ 2865 题面 给出一个长度为n (n <= 5e5) 的字符串,对于每一位,求包含该位的.最短的.在原串中只出现过一次的子串. 题解 ...

  7. bzoj 1396: 识别子串 && bzoj 2865: 字符串识别【后缀数组+线段树】

    根据height数组的定义,和当前后缀串i最长的相同串的长度就是max(height[i],height[i+1]),这个后缀贡献的最短不同串长度就是len=max(height[i],height[ ...

  8. BZOJ 2865 字符串识别(后缀数组+线段树)

    很容易想到只考虑后缀长度必须为\(max(height[rk[i]],height[rk[i]+1])+1\)(即\([i,i+x-1]\)代表的串只出现过一次)然后我正着做一遍反着做一遍,再取一个\ ...

  9. [CF653F] Paper task - 后缀数组,线段树,vector

    [CF653F] Paper task Description 给定一个括号序列,统计合法的本质不同子串的个数. Solution 很容易想到,只要在传统统计本质不同子串的基础上修改一下即可. 考虑经 ...

  10. BZOJ.1396.识别子串(后缀自动机/后缀数组 线段树)

    题目链接 SAM:能成为识别子串的只有那些|right|=1的节点代表的串. 设这个节点对应原串的右端点为r[i],则如果|right[i]|=1,即\(s[\ [r_i-len_i+1,r_i-le ...

随机推荐

  1. Python 序列与映射的解包操作-乾颐堂

    解包就是把序列或映射中每个元素单独提取出来,序列解包的一种简单用法就是把首个或前几个元素与后面几个元素分别提取出来,例如: first, seconde, *rest = sequence 如果seq ...

  2. class Qstring has no member named to Ascii

    人家修改了.真的没有toAscii了.不过可以用toLatin1或者qPrintable()

  3. Red Hat 6.5 Samba服务器的搭建(匿名访问,免登录)

    搭建Samba服务器是为了实现Linux共享目录之后,在Windows可以直接访问该共享目录. 现在介绍如何在红帽6.5系统中搭建Samba服务. 搭建Samba服务之前,yum源必须配置好,本地源和 ...

  4. c语言的函数可以这样写,你见过吗?

    c语言的函数可以这样写,你见过吗?真的可以编译通过的.

  5. 删除右键菜单中的Git Gui Here、Git Bash Here的方法

    修改注册表的方法: 1.点击左下角开始菜单 - 运行(输入regedit)- 确定或者回车: 2.在打开的注册表中找到:HKEY_CLASSES_ROOT,并点HKEY_CLASSES_ROOT前面的 ...

  6. Python2.7.9 编码问题

    最近学一学网络爬虫,遇到第一件头疼的事情就是编码问题, 看了很多教程讲得不清楚, 现在整理一下,希望以后查看方便一些 使用   sys.getdefaultencoding()   查看Python的 ...

  7. Druid详细配置信息

    druid的配置项如下 配置 缺省值 说明 name   配置这个属性的意义在于,如果存在多个数据源,监控的时候 可以通过名字来区分开来.如果没有配置,将会生成一个名字, 格式是:"Data ...

  8. 编写高质量代码改善C#程序的157个建议——建议125:避免用FCL的类型名称命名自己的类型

    建议125:避免用FCL的类型名称命名自己的类型 试想过自己写一个Socket类型吗?如果没有,我们来尝试一下: public class Socket { //省略 } 把以上代码同某些其他工具类封 ...

  9. 洛谷P4234 最小差值生成树(lct动态维护最小生成树)

    题目描述 给定一个标号为从 11 到 nn 的.有 mm 条边的无向图,求边权最大值与最小值的差值最小的生成树. 输入输出格式 输入格式:   第一行两个数 n, mn,m ,表示图的点和边的数量. ...

  10. 51nod 1421 最大MOD值(高妙的调和级数复杂度)

    有一个a数组,里面有n个整数.现在要从中找到两个数字(可以是同一个) ai,aj ,使得 ai mod aj 最大并且 ai ≥ aj. Input 单组测试数据. 第一行包含一个整数n,表示数组a的 ...