最小表示法

模板题

传送门

很疑惑怎么就是蓝题了,这算法不难理解呀(比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;
}
posted @ 2020-11-18 17:22  追梦人1024  阅读(109)  评论(0编辑  收藏  举报