最小表示法
模板题
很疑惑怎么就是蓝题了,这算法不难理解呀(比KMP好多了)
概念
给定一个字符串s,不断把s的最后一个元素放到开头,可以得到n个字符串,其中字典序最小的一个称为s的最小表示.
另外,这n个字符串被称作是循环同构的,为了方便叙述,这里定义b[]
存储s的循环同构字符串,且b[i]
表示以s的第i位开头的字符串
朴素算法
根据定义,枚举所有的b,逐位匹配找出最小值,复杂度O(n^2)
优化算法
首先,将s变化为:s=s+s
,得b[i]=s.substr(i,s.size())
(substr
意义同C++string)
对于任意i,j,在s.substr(i,k-1)==s.substr(j,k-1)
的前提下,若s[i+k] > s[j+k]
,显然,b[i]
不是最小表示.此外,b[i+1],b[i+2]...b[i+k]
均不是最小表示
原因:
对于p(1<=p<=k),存在一个比b[i+p]
更小的循环同构串:b[j+p]
(这里不太好理解,但是仔细想想,或者搞个字符串模拟一下,还是可以理解的)
因此,每次找到s[i+k] > s[j+k]
时,i=i+k+1
,若得到的i等于j,则++i
时间复杂度
结论:O(n)
证:
若每次比较从前向后扫描了k的长度,则i或j二者之一会向后移动k,而i和j合计一共最多向后移动2n的长度,因此时间复杂度为O(n)
模板题代码
#include <iostream>
#include <cstdio>
using namespace std;
int read() {
int re = 0;
bool sig = false;
char c = getchar();
while(c < '0' || c > '9') {
if(c == '-')sig = true;
c = getchar();
}
while(c >= '0' && c <= '9')
re = (re << 1) + (re << 3) + c - '0',
c = getchar();
return sig ? -re : re;
}
int n ;
int a[600010];
int main() {
n = read();
for(int i = 1 ; i <= n ; i++)
a[i] = a[n + i] = read();
int i = 1 , j = 2 , k;
while(i <= n && j <= n) {
for(k = 0 ; k < n && a[i + k] == a[j + k] ; k++);
if(k == n)break;
if(a[i + k] > a[j + k]) {
i = i + k + 1;
if(i == j)++i;
}
else {
j = j + k + 1;
if(i == j)++j;
}
}
int ans = (i < j ? i : j);
for(int p = ans ; p < ans + n ; p++)
printf("%d " , a[p]);
return 0;
}