最小表示法
模板题
定义
给定一个字符串 S [ 1.. n ] S[1..n] S[1..n],如果我们不断地把它的最后一个字符放到字符串的串的开头,最终会得到 n n n个字符串,而这 n n n个字符串中,字典序最小的一个字符串,称为字符串S的最小表示法
朴素算法
将每个字符串存到数组里面,并且将数组进行一个排序,然后输出字符串数组的第一个就是这个字符串 S S S的最小表示法,复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
线性做法
实际上我们可以线性求出一个字符串的最小表示法。
因为是把最后一个字符转移到第一个位置,所以我们可以把字符串,赋值成两倍,这样子就可以断环成列,免去很多的操作
我们先拿一个样例来模拟一下:
求
S
=
"
b
a
c
a
c
a
b
c
"
的
最
小
表
示
法
S="bacacabc"的最小表示法
S="bacacabc"的最小表示法
先断环成列:b a c a c a b c b a c a c a b c
在比较
a
c
a
c
a
b
c
b
aca cabcb
acacabcb 与
a
c
a
b
c
b
a
c
acabcbac
acabcbac的大小时
我们发现两个串中的
c
,
b
c ,b
c,b 字符第一个不相同,所以可以判断出 A
>
>
> B
但是我们不妨想一想,如果在往后比较, A串中还有
c
c
c,B串中还有
b
b
b,那么这两个串的大小关系还是确定的,所以这一次的比较是多余的。那么那些多余的比较可以省略呢?
比如 i + k i+k i+k , j + k j+k j+k 处发现不相等的字符,那么如果 S [ i + k ] > S [ j + k ] S[i+k]>S[j+k] S[i+k]>S[j+k] ,那么我们就可以知道 S [ i . . i + n − 1 ] S[i..i+n-1] S[i..i+n−1]不是最小表示法,并且在 S [ i + 1.. i + n ] − − S [ i + k . . i + n − 1 + k ] S[i+1..i+n]--S[i+k..i+n-1+k] S[i+1..i+n]−−S[i+k..i+n−1+k]都不是,因为字符串B也可以向后移,而且每次都比A小
由此我们可以得出该算法就是两个指针不断向后移动的形式
计算一下复杂度,如果每次比较向后扫描了
k
k
k的长度,那么
i
,
j
i,j
i,j二者之一必定要向后移动,而
i
,
j
i,j
i,j最多移动
2
n
2n
2n此,那么这个算法的复杂度就是
O
(
n
)
O(n)
O(n)
n=read();
for(int i=1;i<=n;i++){a[i]=read();a[i+n]=a[i];}//预处理,断环成列
int i=1,j=2;
while(i<=n&&j<=n)
{
int k=0;
while(a[i+k]==a[j+k])k++;
if(a[i+k]>a[j+k])
{
i+=k+1;//向后移动,处理多余的
if(i==j)i++;
}
else
{
j+=k+1;
if(i==j)j++;
}
}
//在最后一次比较,大的向后移动超出边界,所以小的在范围内
for(int ans=min(i,j);ans<min(i,j)+n;ans++)cout<<a[ans]<<' ';
return 0;
}