最小表示法

最小表示法是用于解决字符串最小表示问题的方法。

字符串的最小表示

循环同构

当字符串 \(S\) 中可以选定一个位置 \(i\) 满足

\[S[i \cdots n]+S[1 \cdots i-1]=T \]

则称 \(S\)\(T\) 循环同构

最小表示

字符串 \(S\) 的最小表示为与 \(S\) 循环同构的所有字符串中字典序最小的字符串

暴力做法

定义 \(best\) 表示当前的最优解的起点位置,初值为 \(1\)

外层循环枚举每个循环同构的串的起点 \(i\)

我们每次比较 \(best\)\(i\) 开始的循环同构,把当前比较到的位置记作 \(j\),每次遇到不一样的字符时便把大的跳过,更新 \(best\),最后以 \(best\) 为起点的串就是最优解。

#include <bits/stdc++.h>
using namespace std;
int n, a[600010], best = 1;
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        a[i + n] = a[i];
    }
    for (int i = 2; i <= n; i++) {
        for (int j = 0; j < n; j++) {
            if (a[best + j] > a[i + j]) {
                best = i;
                break;
            }
            if (a[best + j] < a[i + j]) {
                break;
            }
        }
    }
    for (int i = 0; i < n; i++) {
        printf("%d ", a[i + best]);
    }
    return 0;
}

随机数据下表现良好,但是可以构造特殊数据卡掉。

例如:对于 \(aaa \cdots ab\), 不难发现这个算法的复杂度退化为 \(\mathcal O(n^2)\)

我们发现,当字符串中出现多个连续重复子串时,此算法效率降低,我们考虑优化这个过程。

最小表示法

算法核心

考虑对于一对字符串 \(A,B\), 它们在原字符串 \(S\) 中的起始位置分别为 \(i,h\), 且它们的前 \(k\) 个字符均相同,即

\[A[i \cdots i+k-1]=B[j \cdots j+k-1] \]

不妨先考虑 \(A[i+k]>b[i+k]\) 的情况,我们发现起始位置下标 \(l\) 满足 \(l \in [i,i+k]\) 的字符串均不能成为答案。因为对于任意一个字符串 \(S_{i+p} ~~ (p \in [0,k],\text{表示以} ~ i+p ~ \text{为起始位置的字符串})\) 一定存在字符串 \(S_{j+p}\) 比它更优。

所以我们比较时可以跳过下标 \(l \in [i,i+k]\), 直接比较 \(S_{i+k+1}\)

这样,我们就完成了对于上文暴力的优化。

时间复杂度 \(\mathcal O(n)\)

算法流程

  1. 初始化指针 \(i\)\(0\)\(j\)\(1\)\(S\) 下标从 \(0\) 开始);初始化匹配长度 \(k\)\(0\)

  2. 比较第 \(k\) 位的大小,根据比较结果跳转相应指针。若跳转后两个指针相同,则随意选一个加一以保证比较的两个字符串不同

  3. 重复上述过程,直到比较结束

  4. 答案为 \(i,j\) 中较小的一个

int i=0,j=1,k=0;
while(i<n&&j<n&&k<n){
	if(a[(i+k)%n]==a[(j+k)%n]) k++;
	else{
		a[(i+k)%n]>a[(j+k)%n]?i=i+k+1:j=j+k+1;
		if(i==j) i++;
		k=0;
	}
}
i=min(i,j);
posted @ 2022-10-06 16:32  「ycw123」  阅读(92)  评论(0编辑  收藏  举报