记号
设 为字符串 从第 位到第 位构成的字串。
有一个长度为 的字符串 ,如果我们不断把它开头的字符移到结尾,可以得到 个字符串,这些字符串是循环同构的,其中字典序最小的就是 的最小表示。
我们可以把 复制一份接在后面,将新字符串记作 。
显然,查询时,可以直接查询 来得到第 个循环同构串。
如何求出 的最小表示呢?
我们可以暴力比较 的所有循环同构串,时间复杂度为 。
我们可以维护两个变量 ,,分别代表 和 。, 分别初始为 ,。
比较可以直接使用一个变量 ,按位比较 与 的值。
这个做法明显有优化的空间,怎么优化呢?
我们先看一个例子。
有一个字符串 为 abcabb
, 则为 abcabbabcabb
。

现在 ,,。
我们发现 。
通过这一位的大小,我们可以得出很多的信息:
,不仅意味着 ,还意味着 ,。
综上所述,当 时,因为 且 ,所以对于 , 。
于是,发现差异时,可以直接将那个对应字符较大的变量后移 位。
由于 和 都只会右移,且都最多只会右移 次,时间复杂度为 。
代码
模板题代码:
#include <cstdio>
#define max(a, b) (a > b ? a : b)
#define min(a, b) (a < b ? a : b)
int n, a[600001], ans;
int main(){
scanf("%d", &n);
for (int i = 1;i <= n;i ++) scanf("%d", &a[i]), a[i + n] = a[i]; // 复制一份接在后面
int i = 1, j = 2;
for (;i <= n && j <= n;){
int k = 0;
for (;a[i + k] == a[j + k];) k ++; // 匹配
if (a[j + k] < a[i + k]){
int t = j;
j = max(j + 1, i + k + 1); // 右移(这里取 max 是为了防止前移)
i = t;
}
else j = j + k + 1;
// 注意,这里保证了 j 始终 > i
}
for (int _ = i;_ < i + n;_ ++) printf("%d ", a[_]); // 输出
}