HDU 4333 Revolving Digits(拓展KMP)

链接:

http://acm.hdu.edu.cn/showproblem.php?pid=4333


题目大意:

给一个数字字符串S,  可以把S最后一个数字移动到最前面变成另一个数字。例如123,  经过移动依次变成312,231,123。 注意当移动次数正好和S长度相等时,S又变回了最开始的那个数字。

求这个移动过程所形成的所有字符串,大于S(最初的)的数字,等于S,以及小于S的各有多少个。


分析与总结:

1. 首先要考虑以怎样的形式保存S,因为以往题目一般的移动方式都是最左边的数字移动到最右边的,只需要复制2倍的即可。 而这题是把最右边的移动到最左边的,也可以这样处理。虽然它是最右边的移到最左边的,但是我们可以逆过来想,从最终状态变为就初状态,就相当于“恢复”的过程:把最左边的数字移动到最右边。


2. 然后是枚举起点比较。如果朴素的方法复杂度为O(n^2),肯定超时,所以要想到用一个线性时间的方法。

    再仔细一看,发现其实就是裸的拓展KMP了:设原来字符串为T, 长度为len, 那么复制两倍后的为S, S的前len个数就是最初的数字。接下来求T的所有后缀与T的最长公共长度。求这个,是为了节省比较的时间,假设有两个字符串aabbde, aaberf,那么已经知道了前面三个数字是相同的了,只需要比较第4个数即可。 这样,比较只需要O(1)的复杂度即可。那么总的复杂度就是O(n).


3. 这题还要考虑去重的情况,可能移动的过程会有多个相同的数字。之所以会有多个相同的数,是因为有循环节。所以只需要求出最小循环节,枚举这最小循环节之内的情况即可。


4. 拓展KMP求最短循环节的方法:

// 已经求出next数组
int kk;  // kk保存最短循环节
for(int i=1; i<=len; ++i){
    if(i+next[i]>=len){
        kk = len%i ? len : i;
        break;
    }
}



代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int MAXN = 10^100000*2; 
char S[MAXN];
int  f[MAXN];

void getNext(char* T, int* next){
    int len=strlen(T), a=0;
    next[0] = len;
    while(a<len-1 && T[a]==T[a+1]) ++a;
    next[1] = a;
    a=1;
    for(int k=2; k<len; ++k){
        int p=a+next[a]-1, L=next[k-a];
        if(k-1+L >= p){
            int j = max(p-k+1, 0);
            while(k+j<len && T[k+j]==T[j]) ++j;
            next[k] = j;
            a=k;
        }
        else
            next[k] = L;
    }
}

int main(){
    int cas=1,nCase;
    scanf("%d",&nCase);
    while(nCase--){
        scanf("%s",S);
        int len=strlen(S);
        for(int i=0; i<len; ++i){
            S[i+len] = S[i];
        }
        S[2*len] = '\0';
        getNext(S,f);
        int L=0, E=0, G=0;
        // 拓展KMP求最短循环节
        int kk;
        for(int i=1; i<=len; ++i){
            if(i+f[i]>=len){
                kk = len%i ? len : i;
                break;
            }
        }
        for(int i=0; i<kk; ++i){
            if(f[i]>=len)
                ++E;
            else if(f[i]>0 && f[i]<len){
                if(S[i+f[i]] > S[f[i]])
                    ++G;
                else
                    ++L;
            }
            else if(f[i]==0){
                if(S[i]>S[0])
                    ++G;
                else
                    ++L; 
            }
        }
        printf("Case %d: %d %d %d\n",cas++,L,E>0,G);
    } 
    return 0;
}



 ——  生命的意义,在于赋予它意义士。

          原创 http://blog.csdn.net/shuangde800 , By   D_Double  (转载请标明)







  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值