赞
踩
感谢这篇博客:字符串的最小表示法
引言:一个长度为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; }
链接: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; }
链接: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; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。