题目描述
程序员 Billy "Hacker" Geits\texttt{Billy "Hacker" Geits}Billy "Hacker" Geits 使用一种特殊的方法来隐藏他的密码。他选择一个由小写拉丁字母组成的字符串 SSS,长度为 LLL,然后进行所有可能的单字母左循环移位(包括原始字符串),并选择这些字符串中字典序最小的一个的某个前缀作为密码。
例如,对于字符串 "alabala",所有循环移位为:
alabala(位置 0)labalaa(位置 1)abalaal(位置 2)balaala(位置 3)alaalah(位置 4)laalaba(位置 5)aalabal(位置 6)
其中字典序最小的是 "aalabal",起始位置为 6。
任务:对于给定的字符串 SSS,找到字典序最小的循环移位的起始位置。如果有多个相同的字典序最小移位,则返回最小的起始位置。
输入格式
- 第一行:测试用例数 TTT
- 接下来 TTT 行:每行包含长度 LLL 和字符串 SSS
输出格式
- TTT 行,每行一个数字表示找到的起始位置
题目分析
问题本质
我们需要在字符串 SSS 的所有循环移位中,找到字典序最小的那个。循环移位是指将字符串的前 kkk 个字符移动到末尾形成的新字符串。
暴力方法的局限性
最直接的方法是生成所有 LLL 个循环移位,然后进行排序。这种方法的时间复杂度为 O(L2logL)O(L^2 \log L)O(L2logL),对于 LLL 最大为 100000100000100000 的情况完全不可行。
高效算法选择
Booth\texttt{Booth}Booth 算法(最小表示法)可以在 O(L)O(L)O(L) 时间内解决这个问题。该算法通过双指针技巧,在线性时间内找到最小循环移位。
解题思路
Booth\texttt{Booth}Booth 算法原理
-
双倍字符串技巧: 将原字符串复制一份接在后面,这样从任意位置开始取长度为 LLL 的子串就是一个循环移位。
-
双指针比较:使用两个指针 iii 和 jjj 表示当前比较的两个候选起始位置,kkk 表示当前已匹配的字符数。
-
比较规则:
- 如果 S[i+k]=S[j+k]S[i+k] = S[j+k]S[i+k]=S[j+k]:继续比较下一个字符 (kkk++)
- 如果 S[i+k]>S[j+k]S[i+k] > S[j+k]S[i+k]>S[j+k]:说明从 iii 开始的串不可能最小,将 iii 移动到 i+k+1i+k+1i+k+1
- 如果 S[i+k]<S[j+k]S[i+k] < S[j+k]S[i+k]<S[j+k]:说明从 jjj 开始的串不可能最小,将 jjj 移动到 j+k+1j+k+1j+k+1
-
特殊情况处理:如果 i=ji = ji=j,让其中一个指针前进一位。
-
终止条件:当任一指针超过 LLL 或 k≥Lk \geq Lk≥L 时停止,返回 min(i,j)\min(i, j)min(i,j)。
算法正确性证明
该算法正确性的关键在于:
- 当发现 S[i+k]>S[j+k]S[i+k] > S[j+k]S[i+k]>S[j+k] 时,所有从 iii 到 i+ki+ki+k 的起始位置都不可能是最小表示
- 指针 iii 和 jjj 都只向前移动,不会回退
- 总比较次数不超过 2L2L2L,保证 O(L)O(L)O(L) 时间复杂度
复杂度分析
- 时间复杂度:O(L)O(L)O(L),每个字符最多被比较两次
- 空间复杂度:O(1)O(1)O(1),只使用常数个变量(如果使用取模技巧避免实际构造双倍字符串)
代码实现
// Hidden Password
// UVa ID: 1314
// Verdict: Accepted
// Submission Date: 2025-11-10
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int findMinRotation(const string& s) {
int n = s.length();
int i = 0, j = 1, k = 0;
while (i < n && j < n && k < n) {
char a = s[(i + k) % n];
char b = s[(j + k) % n];
if (a == b) {
k++;
} else {
if (a > b) {
i = i + k + 1;
} else {
j = j + k + 1;
}
if (i == j) {
j++;
}
k = 0;
}
}
return min(i, j);
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int l;
string s;
cin >> l >> s;
cout << findMinRotation(s) << "\n";
}
return 0;
}
代码说明
-
findMinRotation函数:- 使用取模运算
(i + k) % n来模拟双倍字符串,避免实际分配内存 - 维护三个变量:
i、j(候选位置)和k(已匹配长度) - 按照 Booth\texttt{Booth}Booth 算法的比较规则移动指针
- 使用取模运算
-
主函数:
- 使用
ios_base::sync_with_stdio(false)和cin.tie(nullptr)加速输入输出 - 读取测试用例数 TTT
- 对每个测试用例,读取长度 LLL 和字符串 SSS,计算并输出结果
- 使用
样例验证
样例输入:
2
6 babaa
7 alabala
运行过程:
- 对于
"babaa":找到最小移位起始位置为 1 - 对于
"alabala":找到最小移位起始位置为 6
输出:
1
6
与题目样例完全一致。
总结
Booth\texttt{Booth}Booth 算法是解决最小循环移位问题的经典算法,其核心思想是通过双指针竞争和利用已匹配信息来避免不必要的比较。该算法不仅效率高(O(L)O(L)O(L)),而且实现简洁,是处理此类字符串问题的有力工具。
173

被折叠的 条评论
为什么被折叠?



