字符串的最大最小表示法
感谢这篇博客:字符串的最小表示法
引言:一个长度为n的字符串可以将最后一位放在第一位,这样就有n种变形。如"bcaxe" 可以变成 :ebcax xebca axebc caxeb 这几种。
字符串的最大最小表示法,就是求着n种变形中的最大最小字典序的字符串,返回的值是第一个最大最小的字符串的起始字符位置,时间复杂度为O(n)。
思想:(最小表示法)
就是定义三个下标初始 i = 0, j = 1, k = 0;
(1) 如果s[i] < s[j] 那么 j++;
(2) 如果s[i] > s[j]明显 i 不是最小,i = j++;
(3) 如果s[i] == s[j], 那么就要用到 k ,以及一个性质,在 i 和 j 之间的字符一定是大于s[i]和s[j]的。令k = 0, 循环寻找第一个s[i+k] != s[j+k]的位置。若 s[i+k] < s[j+k] 那么 j += k+1, 因为s[i+k] < s[j+k] 那么从 i 开始的在i+k位是要比 j 开始的在j+k位更好一些。直接将j += k+1,然后在继续上面的s[i] 和 s[j] 的比较就行。在这里i 和 j 是等价的。 若s[!+k] > s[j+k], 与上述同理,直接令 i += k+1就行。
注意的是 i+k 和 j+k 可能会大于 n, 就要将其 %n。
下面贴代码:
#include <bits/stdc++.h>
using namespace std;
int GetMin(char *s){
int n = strlen(s);
int i = 0, j = 1, k = 0, t;
while(i < n && j < n && k < n){
t = s[(i+k)%n] - s[(j+k)%n];
if(!t) k++;
else{
if(t > 0) i += k+1;
else j += k+1;
if(i == j) j++;
k = 0;
}
}
return i < j ? i : j;
}
int GetMax(char *s){
int n = strlen(s);
int i = 0, j = 1, k = 0, t;
while(i < n && j < n && k < n){
t = s[(i+k)%n] - s[(j+k)%n];
if(!t) k++;
else{
if(t > 0) j += k+1;
else i += k+1;
if(i == j) j++;
k = 0;
}
}
return i < j ? i : j;
}
char a[10];
int main()
{
scanf("%s", a);
cout << GetMin(a) << " " << GetMax(a) << endl;
return 0;
}
例题
1.hdu3374 String Problem
链接:hdu3374
题意:多组输入,每组一个字符串,要求输出最小表示法的第一个位置和最小表示法出现的次数 以及 最大表示法的第一个位置和出现次数。
题解:按照上述方法求出最大最小表示法的第一个位置,如何判断出现的次数就要用大KMP的Next数组了,考虑要的到出现的次数,只有当字符串是循环出现的,才有可能在变换后出现相同的字符串。用Next的第一个性质,求出最小循环节tmp = len-Next[len], 判断len%tmp 是否为0,若为0,则表示是循环字符串,最大最小出现次数就是len/tmp。否则出现次数为1。
代码:
const int maxn = 1e6+50;
int m, n;
int Next[maxn];
char str[maxn], mo[maxn];
void GetNext()
{
m = strlen(mo);
Next[0] = -1;
int i = 0, j = -1;
while(i < m){
if(j == -1 || mo[i] == mo[j]){
i++; j++; Next[i] = j;
}
else{
j = Next[j];
}
}
return ;
}
int GetMin(char *s)
{
n = strlen(s);
int i = 0, j = 1, k = 0, t;
while(i < n && j < n && k < n){
t = s[(i+k)%n] - s[(j+k)%n];
if(!t) k++;
else{
if(t > 0) i += k+1;
else j += k+1;
if(i == j) j++;
k = 0;
}
}
return i < j ? i : j;
}
int GetMax(char *s)
{
n = strlen(s);
int i = 0, j = 1, k = 0, t;
while(i < n && j < n && k < n){
t = s[(i+k)%n] - s[(j+k)%n];
if(!t) k++;
else{
if(t > 0) j += k+1;
else i += k+1;
if(i == j) j++;
k = 0;
}
}
return i < j ? i : j;
}
int main()
{
while(scanf("%s", mo) != EOF){
GetNext();
int Min = GetMin(mo) + 1;
int Max = GetMax(mo) + 1;
int tmp = m - Next[m];
if(m % tmp == 0 && m != tmp){
printf("%d %d %d %d\n", Min, m/tmp, Max, m/tmp);
}
else{
printf("%d 1 %d 1\n", Min, Max);
}
}
return 0;
}
2.hdu2609 How many
链接:hdu2609
题意:多组,每组第一行一个n表示该组有n个字符串,每组字符串都是由’0‘和’1‘组成,并且长度相等。现在你可以对任意个字符串进行任意次操作,每次操作是将该字符串的最后一个字符放到最前面,现在让你求出现的最小个数的不同的字符串的个数。
题解:就是说尽可能的将不同的字符串转化为相同字符串,若俩个字符串可以变成一样的,那么他们的最小(大)表示法的字符串一定一样,将每个字符串转化为最小表示法,在用一个set去重就可以了。
代码:
int n, m;
string str;
set<string> Set;
int GetMin(string s){
m = s.size();
int i = 0, j = 1, k = 0, t;
while(i < m && j < m && k < m){
t = s[(i+k)%m] - s[(j+k)%m];
if(!t) k++;
else{
if(t > 0) i += k+1;
else j += k+1;
if(i == j) j++;
k = 0;
}
}
return i < j ? i : j;
}
int main()
{
while(scanf("%d", &n) != EOF){
Set.clear();
while(n--){
cin >> str;
int Min = GetMin(str);
string tmp = str.substr(Min) + str.substr(0, Min);
Set.insert(tmp);
}
printf("%d\n", Set.size());
}
return 0;
}