题目描述
小明发现了一个很好玩的事情,他对一个数作旋转操作,把该数的最后的数字
移动到最前面。比如,数 123 可以得到 312,231,123,这样就可以得到很多
个数。 现在,小明的问题是这些数中,有多少个不同的数小于原数,多少个等于原数,
多少个大于原数。 旋转中可能会出现前导零,两数比较的时候可以忽略前导零的影响。 输入格式
输入一个整数 N(0< N≤10^100000)。
输出格式
答案在一行中输出三个整数,分别是小于 N,等于 N,大于 N 的个数,中间
以空格隔开。
样例输入
341
样例输出
1 1 1
初步分析
如果暴力做在这道题的话复杂度为O(N^2)对于比较大的数据的话肯定会卡住。所以得另寻道路。
我们不妨先用一个例子来找找有没有什么简便的方法。
设字符串为12312312,将最后一个字母放在最前面时:21231231.(2与1比较)。当后面两个放到最前面时12123123(前面12都是相同的,直接比较第3个)。现在我们想象扩展KMP里面的next数组的定义next[i]表示T[i…n-1]与T的最大公共前缀。那这不就是我们移动之后所进行的从第i个开始与第1个开始依次匹配嘛。很显然前面的next[i]次匹配都是相同的(想想next的定义).所以我们直接比较第一个不相同的字符就行了。无非比它大或者比它小,只比较依次就行了。(比较str[(i+next[i])%len]与str[next[i]%len]的大小)所以整体的复杂度直接降到了O(N)。
用例优化
我们再想想,还有没有情况可以优化的呢。就是可以把复杂度降到O(N)以下的情况。
是有的哦,当这个字符串是由一个最小子串重复出现的时候。比如abcabcabcabc…这种情况。我们需要比较N次吗。没必要,很容易就想到只需要比较移动最后一组最小子串的情况就行了。其他组子串的匹配结果与最后一组子串的结果是一样的。所以最后的结果全部乘以组数就行了。
那么怎么检查一个字符串是不是又一个最小子串重复得到的呢。
我们需要用到KMP里面的fail数组(有的地方也称之为next数组)。先说结论int mix_n=n-fail[n-1]-1;
if(n%min_n == 0 ) 这个字符串的最小重复子串的长度就是min_n.其实这个结论很简单推导的。比如 T a b c a b c a b c…
i: 0 1 2 3 4 5 6 7 8…
fail[i] -1 -1 -1 0 1 2 3 4 5…
只有最前面的第一组最小重复子串的fail值全是-1,后面就从0开始依次增加了。所以n-fail[n-1]-1就等于最小重复子串的长度。并且(n%min_n==0 && min_n!=n)时这个字符串才全是由最小重复子串组成的。
直接上代码
#include <iostream>
#include <string.h>
using namespace std;
const int MAX_N=1000000;
int biger=0;
int smaller=0;
int Equal=0;
int next[MAX_N];
int fail[MAX_N];
char str[MAX_N];
void Getnext(char* str){
int i=0,p0,len=strlen(str);
next[0]=len;
while(str[i+1]==str[i] && i+i<len)
++i;
next[1]=i;
p0=1;
for(int i=2;i<len;++i){
if(next[i-p0]+i<next[p0]+p0)
next[i]=next[i-p0];
else{
int j=next[p0]+p0-i;
if(j<0) j=0;
while(i+j<len && str[i+j]==str[j])
++j;
next[i]=j;
p0=i;
}
}
}
void Getfail(char* s) {
int match=-1;
fail[0]=-1;
for(int i=1;s[i];++i){
while(match >=0 && s[match+1]!=s[i]){
match=fail[match];
}
if(s[match+1] == s[i]){
match++;
}
fail[i]=match;
}
}
int main() {
scanf("%s",&str);
Getnext(str);
Getfail(str);
int over=0;
int len=strlen(str);
int x=len-fail[len-1]-1;
if(len%x == 0) over=len-x;
for(int i=len-1;i>=over;i--){
if(str[(i+next[i])%len]>str[next[i]%len])
biger++;
else if(str[(i+next[i])%len]<str[next[i]%len])
smaller++;
else
Equal++;
}
if(over!=0)
{
int x=len/(smaller+biger+Equal);
cout<<smaller*x<<" "<<biger*x<<" "<<Equal*x<<endl;
}
else
cout<<smaller<<" "<<biger<<" "<<Equal<<endl;
return 0;
}