soj 3296: Windy's S

@(K ACMer)


题意:
求所给字符串的循环表示的最小字典序.
分析:
循环字符串的最小表示集裸题.
最朴素的方法是 O(n2) 的复杂度,显然超时.这里用了一个很巧妙的性质来剪枝,使复杂度降到 O(n) .

字符串循环的最小独立集

这里引入同构的知识,我们有树的同构,图的同构,和字符串的同构.其所为同构是指他们在进行一些可取的等效操作(比如树和图的旋转,字符串的移位)后是相同的,即本质相同.
求一个可循环移位的字符串的最小字典表示.
用来判断两个字符串是否循环同构.(这里也可以用kmp算法 O(n) 的实现).
这里先很容易的提出暴力的算法:

int getmins(void) {
    int i = 0, j = 1, k = 0;
    while (k < l && i < l && j < l) {
        if (s[j] < s[i]) i = j, j = i + 1;
        else if (s[j] > s[i]) j++;
        else {
            k = 0;
            while (k < l) {
                int t = s[(j + k) % l] - s[(i + k) % l];
                if (t > 0) {
                    j = j + 1;   //替换1
                    break;
                } else if (t < 0) {
                    i = j;   
                    j = i + 1;
                    break;
                } else {
                    k++;
                }
            }
        }
    }
    return i;
}

这个暴力算法很明显是 O(n2) 的下届.但是这里我们可以有个非常强大的剪枝,观察上午代码中替换1的注释那一句.这里我们的j想替换i作为最小的其实位置失败了,所以我们就把 j++ 来继续枚举下一个想来替换i的位置.但是我们发现没有其实在 [j,j+k] 范围内的所有其实位置都已经没有必要计算了它们都不可能比i为起始位置的字符串小,来替换以i为起始位置的字符串.所以我们可以直接把j替换为 (j+k+1) 位置来进行比较.
改进后的代码:

int getmins(void) {
    int i = 0, j = 1, k = 0;
    while (k < l && i < l && j < l) {
        if (s[j] < s[i]) i = j, j = i + 1;
        else if (s[j] > s[i]) j++;
        else {
            k = 0;
            while (k < l) {
                int t = s[(j + k) % l] - s[(i + k) % l];
                if (t > 0) {
                    j = j + k + 1;   //替换1
                    break;
                } else if (t < 0) {
                    i = j;
                    j = i + 1;
                    break;
                } else {
                    k++;
                }
            }
        }
    }
    return i;
}

code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <string>
#include <queue>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int mod = int(1e9) + 7, INF = 0x3f3f3f3f, maxn = 1e5 + 40;
int T, l;
char s[maxn];

//令i表示当前最小串,j表示最小串的搜索指针.
int getmins(void) {
    int i = 0, j = 1, k = 0;
    while (k < l && i < l && j < l) {
        if (s[j] < s[i]) i = j, j = i + 1;
        else if (s[j] > s[i]) j++;
        else {
            k = 0;
            while (k < l) {
                int t = s[(j + k) % l] - s[(i + k) % l];
                if (t > 0) {
                    j = j + k + 1;   //替换1
                    break;
                } else if (t < 0) {
                    i = j;
                    j = i + 1;
                    break;
                } else {
                    k++;
                }
            }
        }
    }
    return i;
}


int main(void)
{
    scanf("%d", &T);
    while (T--) {
        getchar();getchar();
        scanf("%s", s);
        l = (int)strlen(s);
        int maxs = getmins();
        for (int i = maxs, cnt = 0; cnt < l; cnt++, i++) {
            if (i == l) i = 0;
            printf("%c", s[i]);
        }
        printf("\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值