字符串大师
字符串大师
题意
一个串 \(T\) 是 \(S\) 的循环节,当且仅当存在正整数 \(k\),使得 \(S\) 是 \(T^k\) (\(T\) 重复 \(k\) 次)的前缀。
给出字符串 \(S\) 每个前缀的最短循环节,求出字典序最小的 \(S\)。
思路
记字符串 \(S\) 的最短循环节为 \(d\),有:
证明:
先证明引理:对于循环节 \(d\) 有 \([1,\text{len}(S)-d]=[d,\text{len}(S)]\)。
分类讨论,若循环节为完全循环,引理显然成立。
若循环节为不完全循环,记 \(\text{len(S)} \bmod d=p\),
则 \([d,\text{len}(S)]\) 缺失的 \(p-d\) 个字符位于 \([\text{len}(S)-d+1,\text{len}(S)-p]\),
\([1,\text{len}(S)-d]\) 同时也缺失了这一部分的字符。综上,引理得证。
根据引理:对于循环节 \(d\) 有 \([1,\text{len}(S)-d]=[d,\text{len}(S)]\),
再加上 \(d\) 为最短循环节,所以 \([1,\text{len}(S)-d],[d,\text{len}(S)]\) 都是 \(\text{border(S)}\),得证。
所以可以先用 \(\text{len}(S)-d\) 算出 \(\text{border}(S)\),再通过反向的 KMP 还原字符串。
考虑 KMP 求 \(\text{border}\) 的过程:
首先令 \(\text{border}(1)=0\)。
对于 \(i \in [2,n]\),
令 \(t = \text{border}(i-1)\),重复 \(t \leftarrow \text{border}(t)\) 直到 \(t = 0\) 或 \(S_{t+1} = S_i\)。
若最后 \(t = 0\) 则 \(\text{border}(i)=0\),否则 \(\text{border}(i)=t+1\)。
反过来同理。
首先令 \(S_1=A\),因为要满足字典序最小。
对于 \(i\in[2,n]\),
令 \(t = \text{border}(i-1)\),重复 \(t\leftarrow\text{border}(t)\) 直到 \(t=0\) 或 \(t +1=\text{border}(i)\)。
每次 \(t\leftarrow\text{border}(t)\) 前,根据 KMP 求 \(\text{border}\) 的过程可知,\(S_{t+1}\ne S_i\)。
若最后 \(t\ne 0\),可知 \(S_{t+1}=S_i\),直接赋值即可。
若 \(t = 0\),可通过上过程得到的若干组不等关系找出一个最小满足条件的 \(S_i\)。
时间复杂度:\(O(n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, fail[N];
char s[N];
bool neq[30];
int main() {
cin >> n;
for (int i = 1; i <= n; i ++) {
cin >> fail[i];
fail[i] = i - fail[i];
}
s[1] = 'a';
for (int i = 2, t; i <= n; i ++) {
memset(neq, 0, sizeof(neq));
for (t = fail[i - 1]; fail[i] != t + 1; t = fail[t]) {
neq[s[t + 1] - 'a'] = 1;
if (!t) break;
}
if (fail[i] == t + 1) s[i] = s[t + 1];
else {
for (char x = 'a'; x <= 'z'; x ++) {
if (!neq[x - 'a']) {
s[i] = x;
break;
}
}
}
}
puts(s + 1);
return 0;
}
本文来自博客园,作者:maniubi,转载请注明原文链接:https://www.cnblogs.com/maniubi/p/18517403,orz