Page Top

字符串——最小表示法 学习笔记

字符串——最小表示法 学习笔记

定义

在字符串 \(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;
}
posted @ 2024-05-23 12:22  RainPPR  阅读(24)  评论(0编辑  收藏  举报