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

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

Description

给定一个字符串 S,要求一个最短的字符串 T,使得 S 可以由 T 不断在后面接上自身得到。在拼接的时候, T 的某个后缀如果与某个前缀相同,则相同的部分可以算作一个,不再重复出现。

Limitations

1|S|5×105

Solution

介绍一个叫 border 树的东西,在 OI 中被称作 next 树。

S 的前缀 i 的最长 borderborderi,考虑在 iborderi 之间连一条边,最终会形成一棵以 0 为根的树。

证明上,考虑这棵树有 n+1 个节点,而显然 borderi<i,因此每个节点连向 border 的边都是互不重复的,共有 n 条边,由此可以证明这是一颗树。

这棵树有两个优美的性质:

节点 u 的祖先集合是 u 的所有 border 集合

节点 u 的后代集合是 u 能作为 borderS 的前缀子串集合

对于性质 1,根据定义,u 的父节点是 u 的最长 border,迭代证明即可。

性质 2 可以由性质 1 反推得到。

现在考虑本题。

一个显而易见的结论是 T 一定是 Sborder

因此我们考虑枚举 S 的所有 border,我们发现对于长度为 iborder,如果将他在 border 树上的后代拿下来排序以后相邻两数差值的最大值大于 i,则这个 border 不能作为答案,因为对于插值最大的两个数,在拼接到左边的位置以后再加一个长度为 iT 不能拼接到右侧的数,反之可以证明这个 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);
  }
}
posted @   一扶苏一  阅读(238)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2018-09-06 【离散化】离散化
2018-09-06 【树状数组】【P2345】 奶牛集会
点击右上角即可分享
微信分享提示