【border相關】【P3426】 [POI2005]SZA-Template
Description
給定一個字符串 \(S\),要求一個最短的字符串 \(T\),使得 \(S\) 可以由 \(T\) 不斷在後面接上自身得到。在拼接的時候, \(T\) 的某個後綴如果與某個前綴相同,則相同的部分可以算作一個,不再重複出現。
Limitations
\(1 \leq |S| \leq 5 \times 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, '\n', 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);
}
}