hdu4333
题意
给出一个长度为 (n) 的数字,每次可以将最后一位放到前面,那么一共会有 (n) 个数字,问有几个不同的数字比原来的数字大,小或相等。
分析
将字符串扩展到原来的两倍,然后求一发 EXKMP ,得到 (nxt) 数组表示后缀串与整个串的最长公共前缀长度,那么要么有下个一个字符不同,或者全部相同,对于后者判断是否产生了循环节,对于前者直接比较大小即可。
注意求的是有几个不同的数字,所以一旦发现存在最小长度的循环节后就要退出循环。
code
#include <bits/stdc++.h>
using namespace std;
// 调用前
// nxt[0] = 0;
// exkmp(s2 + 1, s2, nxt + 1, nxt); // s2 作为模板串
// exkmp(s1, s2, ex, nxt);
// O(n+m)的复杂度下求出字符串s1的任意后缀与字符串s2的最长公共前缀
// nxt[i]==j 表示s2的以s2[i]为起始的后缀与串本身的最长公共前缀长度j
// ex[i]为s1[i..]与s2的最长公共前缀,nxt[i]为s2[i..]与s2的最长公共前缀
void exkmp(char s1[], char s2[], int ex[], int nxt[]) {
int i = 0, j = 0, p = -1;
while(s1[i]) {
if(p == -1) {
j = 0;
do p++;
while(s1[i + p] && s1[i + p] == s2[j + p]);
ex[i] = p;
} else if(nxt[j] != p) ex[i] = min(p, nxt[j]);
else {
j = 0;
while(s1[i + p] && s1[i + p] == s2[j + p]) p++;
ex[i] = p;
}
i++; j++; p--;
}
ex[i] = 0;
}
const int MAXN = 2e5 + 10;
char s[MAXN];
int nxt[MAXN];
int main() {
int T;
scanf("%d", &T);
for(int kase = 1; kase <= T; kase++) {
nxt[0] = 0;
scanf("%s", s);
int len = strlen(s);
for(int i = len; i < 2 * len; i++) s[i] = s[i - len];
s[2 * len] = 0;
exkmp(s + 1, s, nxt + 1, nxt);
int L = 0, G = 0;
for(int i = 1; i < len; i++) {
int x = nxt[i];
if(min(x, len - i) + i == len && len % i == 0) { // 说明存在循环节,那么后面都是重复的
break;
}
if(x < len) {
if(s[x] < s[i + x]) G++;
else L++;
}
}
printf("Case %d: %d 1 %d
", kase, L, G);
}
return 0;
}