字符串大师

字符串大师

题意

一个串 \(T\)\(S\) 的循环节,当且仅当存在正整数 \(k\),使得 \(S\)\(T^k\)\(T\) 重复 \(k\) 次)的前缀。

给出字符串 \(S\) 每个前缀的最短循环节,求出字典序最小的 \(S\)

思路

记字符串 \(S\) 的最短循环节为 \(d\),有:

\[\text{len}(S)-\text{border}(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;
}
posted @ 2024-10-31 11:44  maniubi  阅读(4)  评论(0编辑  收藏  举报