hdu 5442 Favorite Donut(kmp+最小表示法)

题意:

有一个 len 长度的环,问有没有字典序最大长度为 len 的串在 这个环里。
如果有的话,且只有一个 ,输出其开头的下标(下标从1 开始
再输出0表示顺时针(从左至右),1 表示逆时针(从右至左)
如果多个,输出开头下标最小的那个。
如果顺时针,逆时针的字典序一样,且开头下标一样,优先输出输出顺时针

解析:

参考了别人的题解,用最小表示法来做。
循环字符串的最小表示法的问题可以这样描述:对于一个字符串S,求S的循环的同构字符串S’中字典序最小的一个
先给出最小表示法最大表示法的模板:

int getMinString(char s[]) {  
    int len = strlen(s);
    int i = 0, j = 1, k = 0;  
    while(i < len && j < len && k < len) {
        int t = s[(i+k)%len] - s[(j+k)%len];
        if(t == 0) k++;
        else {
            if(t > 0) i += k + 1;
            else j += k + 1;
            if(i==j) j++;
            k = 0;
        }
    }
    return min(i,j);
}

int getMaxString(char s[]) {  
    int len = strlen(s);
    int i = 0, j = 1, k = 0;  
    while(i < len && j < len && k < len) {
        int t = s[(i+k)%len] - s[(j+k)%len];
        if(t == 0) k++;
        else {
            if(t > 0) j += k + 1;
            else i += k + 1;
            if(i==j) j++;
            k = 0;
        }
    }
    return min(i,j);
}

对于正序的字符串可以直接用最大表示法 ,可以得到最大字典序,以及最小的开头位置的下标。

对于逆序就把串翻转一下,用最大表示法,可以得到最大字典序, 也是下标最小开头位置的下标,但是因为颠倒了, 这里的下标是倒序的,所以下标其实是最大的。
但是我们已经得到最大字典序的串了, 所以 接下来把反的串延长一倍,可以利用 kmp 找其中相同串的最大起始位置,其镜像位置就是最小的位置。

然后把两个 最大字典序的串 比较下大小。

正序大输出正序,逆序大输出逆序,相等则输出标号最小的。

my code

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = (int)2e4 + 10;
char str[N], save[N*2];
char str0[N], str1[N];
int pos0, pos1;
int len;

//最大表示法
int getMaxString(char s[]) {  
    int len = strlen(s);
    int i = 0, j = 1, k = 0;  
    while(i < len && j < len && k < len) {
        int t = s[(i+k)%len] - s[(j+k)%len];
        if(t == 0) k++;
        else {
            if(t > 0) j += k + 1;
            else i += k + 1;
            if(i==j) j++;
            k = 0;
        }
    }
    return min(i,j);
}

int Next[N];
void getNext(char s[]) {
    int len = strlen(s);
    int j = 0, k = -1;
    Next[0] = -1;
    while(j < len) {
        if(k == -1 || str[j] == str[k])
            Next[++j] = ++k;
        else k = Next[k];
    }
}

int kmp(char tar[], char pat[]) {
    getNext(pat);
    int res = 0;
    int lenT = strlen(tar), lenP = strlen(pat);
    int j = 0, k = 0;
    while(j < lenT - 1) {
        if(k == -1 || tar[j] == pat[k])
            j++, k++;
        else k = Next[k];

        if(k == lenP) {
            res = max(res, j - lenP);
            k = Next[k];
        }
    }
    return lenP - res - 1;
}

int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        scanf("%d%s", &len, str);

        pos0 = getMaxString(str);
        for(int i = 0; i < len; i++) str0[i] = str[(i + pos0) % len];
        str0[len] = '\0';

        strcpy(save, str);
        reverse(save, save + len);

        pos1 = getMaxString(save);
        for(int i = 0; i < len; i++) str1[i] = save[(i + pos1) % len];
        str1[len] = '\0';

        for(int i = 0; i < len; i++) save[i+len] = save[i];
        save[len*2] = '\0';
        pos1 = kmp(save, str1);

        pos0++, pos1++;
        int cmp = strcmp(str0, str1);
        if(cmp > 0) {
            printf("%d 0\n", pos0);
        }else if(cmp < 0) {
            printf("%d 1\n", pos1);
        }else {
            if(pos0 <= pos1) printf("%d 0\n", pos0);
            else printf("%d 1\n", pos1);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值