最小表示法
最小表示法是用于解决字符串最小表示问题的方法。
字符串的最小表示
循环同构
当字符串 \(S\) 中可以选定一个位置 \(i\) 满足
则称 \(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+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)\)
算法流程
-
初始化指针 \(i\) 为 \(0\),\(j\) 为 \(1\) (\(S\) 下标从 \(0\) 开始);初始化匹配长度 \(k\) 为 \(0\)
-
比较第 \(k\) 位的大小,根据比较结果跳转相应指针。若跳转后两个指针相同,则随意选一个加一以保证比较的两个字符串不同
-
重复上述过程,直到比较结束
-
答案为 \(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);