字符串——最小表示法 学习笔记
字符串——最小表示法 学习笔记
定义
在字符串 \(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)删除。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10亿数据,如何做迁移?
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 易语言 —— 开山篇
· Trae初体验