【border相关】【P3426】 [POI2005]SZA-Template
Description
给定一个字符串 (S),要求一个最短的字符串 (T),使得 (S) 可以由 (T) 不断在后面接上自身得到。在拼接的时候, (T) 的某个后缀如果与某个前缀相同,则相同的部分可以算作一个,不再重复出现。
Limitations
(1 leq |S| leq 5 imes 10^5)
Solution
介绍一个叫 (border) 树的东西,在 OI 中被称作 (next) 树。
记 (S) 的前缀 (i) 的最长 (border) 为 (border_i),考虑在 (i) 和 (border_i) 之间连一条边,最终会形成一棵以 (0) 为根的树。
证明上,考虑这棵树有 (n + 1) 个节点,而显然 (border_i < i),因此每个节点连向 (border) 的边都是互不重复的,共有 (n) 条边,由此可以证明这是一颗树。
这棵树有两个优美的性质:
节点 (u) 的祖先集合是 (u) 的所有 (border) 集合
节点 (u) 的后代集合是 (u) 能作为 (border) 的 (S) 的前缀子串集合
对于性质 (1),根据定义,(u) 的父节点是 (u) 的最长 (border),迭代证明即可。
性质 (2) 可以由性质 (1) 反推得到。
现在考虑本题。
一个显而易见的结论是 (T) 一定是 (S) 的 (border)。
因此我们考虑枚举 (S) 的所有 (border),我们发现对于长度为 (i) 的 (border),如果将他在 (border) 树上的后代拿下来排序以后相邻两数差值的最大值大于 (i),则这个 (border) 不能作为答案,因为对于插值最大的两个数,在拼接到左边的位置以后再加一个长度为 (i) 的 (T) 不能拼接到右侧的数,反之可以证明这个 (border) 是合法的。
我们考虑维护 (border) 的所有后代,从长到短枚举 (border) 时,相当于从 (border) 树的某个叶节点一直枚举到根,我们发现 (border) 变短时只会加入一些节点而不会删除,因此用一个 set
去维护这些后代,用 multiset
维护插值最大值即可。
时间复杂度 (O(|S| log |S|))
Code
写代码的时候发现一个有关 multiset
的有趣的事:erase某个值的时候,会将全部的该值删掉,如果想要只删掉一个,需要 s.erase(s.find(x))
。
#include <cstdio>
#include <set>
#include <vector>
#include <algorithm>
const int maxn = 500005;
int n, ans;
char MU[maxn];
int border[maxn];
std::set<int>s;
std::multiset<int>ms;
std::vector<int>son[maxn];
void KMP();
int ReadStr(char *p);
void dfs(const int u);
void update(const int x);
void KMP();
int main() {
freopen("1.in", "r", stdin);
n = ReadStr(MU + 1);
KMP();
update(n);
for (int i = border[n], j = n; i; j = i, i = border[i]) {
update(i);
for (auto u : son[i]) if (u != j) {
dfs(u);
}
if (*(--ms.end()) <= i) {
ans = i;
}
}
qw(ans, '
', true);
return 0;
}
int ReadStr(char *p) {
auto beg = p;
do *p = IPT::GetChar(); while ((*p > 'z') || (*p < 'a'));
do *(++p) = IPT::GetChar(); while ((*p >= 'a') && (*p <= 'z'));
*p = 0;
return p - beg;
}
void KMP() {
for (int i = 2, j = 0; i <= n; ++i) {
while (j && (MU[i] != MU[j + 1])) j = border[j];
if (MU[i] == MU[j + 1]) ++j;
son[border[i] = j].push_back(i);
}
}
void dfs(const int u) {
update(u);
for (auto v : son[u]) {
dfs(v);
}
}
void update(const int x) {
auto u = s.insert(x).first, ftmp = u, btmp = u;
--ftmp; ++btmp;
if ((u != s.begin()) && (btmp != s.end())) {
ms.erase(ms.find(*btmp - *ftmp));
}
if (u != s.begin()) {
ms.insert(x - *ftmp);
}
if (btmp != s.end()) {
ms.insert(*btmp - x);
}
}