简介
最小表示法,求所有与某个字符串循环同构的字符串中,字典序最小的那个。比如说一个字符串lordash,它长度为7,也就是说最多有七种循环同构的方法。
lordash、ordashl、rdashlo、dashlor、ashlord、shlorda、hlordas。
这几个串在原串上的开始位置分别是0,1,2,3,4,5,6。字典序最小的同构即是以4为起点的那个。
朴素算法
给出一个朴素的算法,我们每次比较i和j开始的循环同构,把当前比较到的位置记作k,每次遇到不一样的字符时便把大的跳过,最后剩下的就是最优解。最坏时间复杂度$ O(|S|^{2}) $。
实现代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | int simple(char* s, int n){
 int i=0, j=1, k=0;
 while(i<n && j<n && k<n)
 {
 if(s[(i+k)%n] == s[(j+k)%n]){
 k++;
 }else{
 if(s[(i+k)%n] > s[(j+k)%n])
 i++;
 else
 j++;
 k = 0;
 if(i == j) i++;
 }
 }
 return min(i, j);
 }
 
 | 
最小表示法O(|S|)
如果比较起始位置\(i\)和起始位置\(j\)发现\(S[i,i+1,\ldots,i+k-1]=S[j,j+1,\ldots,j+k-1]\)且\(S[i+k] \lt S[j+k]\),则起始位置\(j,j+1,\ldots,j+k\)都不合法。对于每个数字\(0 \le l \le k\)都有起始位置\(i+l\)比起始位置\(j+l\)优,因为\(S[i+l,i+l+1, \ldots,i+k-1]=S[j+l,j+l+1,\ldots,j+k-1]\)且\(S[i+k] \lt S[j+k]\) 。
代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | int getmin(char* s, int n){
 int i=0, j=1, k=0;
 while(i<n && j<n && k<n)
 {
 if(s[(i+k)%n] == s[(j+k)%n]){
 k++;
 }else{
 if(s[(i+k)%n] > s[(j+k)%n])
 i+=k+1;
 else
 j+=k+1;
 k = 0;
 if(i == j) i++;
 }
 }
 return min(i, j);
 }
 
 | 
模板
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 
 | char s[mxn];
 int simple(char* s, int n)
 {
 int i=0, j=1, k=0;
 while(i<n && j<n && k<n)
 {
 if(s[(i+k)%n] == s[(j+k)%n]){
 k++;
 }else{
 if(s[(i+k)%n] > s[(j+k)%n])
 i++;
 else
 j++;
 k = 0;
 if(i == j) i++;
 }
 }
 return min(i, j);
 }
 
 int getmin(char* s, int n)
 {
 int i=0, j=1, k=0;
 while(i<n && j<n && k<n)
 {
 if(s[(i+k)%n] == s[(j+k)%n]){
 k++;
 }else{
 if(s[(i+k)%n] > s[(j+k)%n])
 i+=k+1;
 else
 j+=k+1;
 k = 0;
 if(i == j) i++;
 }
 }
 return min(i, j);
 }
 
 int main()
 {
 scanf("%s", s);
 int n = strlen(s);
 printf("%d\n", simple(s, n));
 printf("%d\n", getmin(s, n));
 return 0;
 }
 
 |