【border相關】【P3426】 [POI2005]SZA-Template

【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);
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章