旋转数字

题目描述
小明发现了一个很好玩的事情,他对一个数作旋转操作,把该数的最后的数字
移动到最前面。比如,数 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值