字符串——最小表示法 学习笔记
字符串——最小表示法 学习笔记
定义
在字符串 \(S\) 的所有,与其循环同构的字符串 \(T\) 中,字典序最小的一个。
循环同构:字符串 \(S\) 循环移位,所有可以得到的字符串 \(T\) 与 \(S\) 循环同构。
暴力
枚举与 \(S\) 循环同构的每一个字符串,比较其字典序。
枚举复杂度 \(\mathcal O(n)\),字典序比较复杂度 \(\mathcal O(n)\),整体复杂度 \(\mathcal O(n^2)\)。
优化
可以先将字符串拼接一倍,注意到最小表示一定是以一个 \(k\) 开头的长度为 \(n\) 的字串。
于是,可以用两个指针,分别表示两个比较优化的解,然后考虑扩展。
对于当前的,如果相同,那么扩展长度;如果不同,那么长度清零,然后考虑移动指针。
对于大的,考虑移动,指针向后移动一位,进行下一轮判断。
借用 OI-Wiki 的代码,
k, i, j = 0, 0, 1
while k < n and i < n and j < n:
if sec[(i + k) % n] == sec[(j + k) % n]:
k += 1
else:
if sec[(i + k) % n] > sec[(j + k) % n]:
i += 1
else:
j += 1
k = 0
if i == j:
i += 1
i = min(i, j)
时间复杂度 \(\mathcal O(n^2)\),对于随机数据还可以。
最小表示法
考虑继续优化,注意到如果找到了一组不同的,那么大的可以直接移动下去。
因为对于前面的每一个字串,这一个大的每一个字串都可以唯一的对应一个更优的字符串。
代码依旧借用 OI-Wiki 的:
k, i, j = 0, 0, 1
while k < n and i < n and j < n:
if sec[(i + k) % n] == sec[(j + k) % n]:
k += 1
else:
if sec[(i + k) % n] > sec[(j + k) % n]:
i = i + k + 1
else:
j = j + k + 1
if i == j:
i += 1
k = 0
i = min(i, j)
然后贴一下我的代码:
template<typename tp>
basic_string<tp> mcs(basic_string<tp> a) {
int n = a.size(); a = a + a;
int i = 0, j = 1;
for (int k = 0; k < n && i < n && j < n; ) {
const auto ii = a[i + k], jj = a[j + k];
if (ii == jj) continue(++k);
else if (ii > jj) i = i + k + 1;
else j = j + k + 1;
k = 0; if (i == j) ++j;
}
return a.substr(min(i, j), n);
}
拓展:树的最小表示法
题目:P10477 Subway tree systems.
题目描述:给定每一时刻是远离根(0)还是靠近根(1)。
判断两个给定的操作序列,所对应的两棵树是否同构。
容易发现,我们递归的处理,
- 把每一棵子树的操作序列按照字典序排列即可。
考虑一些比较简单的证明:
一个操作序列一定唯一的对应着一棵树。
这是一个很重要的结论,但是也很好证明。
考虑到每一条边都只能来回经过两次,因此,
当我们进入一个点时,只能新开一个;
当我们退出一个点时,因为是树,因此只存在一个父亲。
然后考虑如何处理。
首先我们知道一个序列,一定开头是 \(0\) 结尾是 \(1\),
因为这两个是用于离开、回到根的。
因此,当我们删去这两个以后,其他的,
我们将 \(0\) 视为 \(1\),将 \(1\) 视为 \(-1\),那么,
如果一个区间前缀和是 \(0\),那么这个区间就是一个完整的子树。
递归处理即可。
-
具体的,递归的把序列分成若干个小块,
-
去掉首位的 \(0\) 和 \(1\),然后扫描一遍。
-
将每一小块递归处理后,加入一个数组。
-
扫描完成后,将数组排序,拼接,即最小表示。
-
刚开始的时候要加上首位的 \(0\)、\(1\),因为我们的递归是从进入一棵树开始的。
#include <bits/stdc++.h>
using namespace std;
int op[2] = {1, -1};
string mcs(string s) {
int cnt = 0, k = 0;
s = s.substr(1, s.size() - 2);
vector<string> bucket;
for (int i = 0; i < (int)s.size() - 1; ++i) {
cnt += op[s[i] - '0'];
if (cnt == 0) bucket.push_back(mcs(s.substr(k, i - k + 1))), k = i + 1;
}
if (k) bucket.push_back(mcs(s.substr(k)));
else bucket.push_back(s.substr(k));
sort(bucket.begin(), bucket.end());
string ans;
for (const string &i : bucket) ans += i;
return "0" + ans + "1";
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int T; cin >> T;
while (T--) {
string a, b; cin >> a >> b;
a = mcs("0" + a + "1"), b = mcs("0" + b + "1");
puts(a == b ? "same" : "different");
}
return 0;
}
本文来自博客园,作者:RainPPR,转载请注明原文链接:https://www.cnblogs.com/RainPPR/p/18208156
如有侵权请联系我(或 2125773894@qq.com)删除。