hdu3374 String Problem (字符串最小表示)

题目:

输出一个字符串的最小表示时起始位置和最大表示时起始位置。并输出有多少个最小表示和最大表示。

分析:

循环字符串的最小表示法的问题可以这样描述:

对于一个字符串S,求S的循环的同构字符串S’中字典序最小的一个。

由于语言能力有限,还是用实际例子来解释比较容易:
设S=bcad,且S’是S的循环同构的串。S’可以是bcad或者cadb,adbc,dbca。而且最小表示的S’是adbc。
对于字符串循环同构的最小表示法,其问题实质是求S串的一个位置,从这个位置开始循环输出S,得到的S’字典序最小。
一种朴素的方法是设计i,j两个指针。其中i指向最小表示的位置,j作为比较指针。

令i=0,j=1 如果S[i] > S[j] i=j, j=i+1 如果S[i] < S[j] j++ 如果S[i]==S[j]
设指针k,分别从i和j位置向下比较,直到S[i] != S[j]
如果S[i+k] > S[j+k] i=j,j=i+1
否则j++ 返回i

起初,我想在j指针后移的过程中加入一个优化。就是j每次不是加1,而是移动到l位置。其中,l>j且S[l]<=S[j]。但是,即使加入这一优化,在遇到bbb…bbbbbba这样的字符串时复杂度将退化到O(n^2)。

注意到,朴素算法的缺陷在于斜体的情况下i指针的移动太少了。针对这一问题改进就得到了最小表示法的算法。最小表示法的算法思路是维护两个指针i,j。

令i=0,j=1 如果S[i] > S[j] i=j, j=i+1 如果S[i] < S[j] j++ 如果S[i]==S[j]
设指针k,分别从i和j位置向下比较,直到S[i] != S[j]
如果S[i+k] > S[j+k] i=i+k
否则j++ 返回i和j的小者

注意到上面两个算法唯一的区别是粗体的一行。这一行就把复杂度降到O(n)了。
值得一提的是,与KMP类似,最小表示法处理的是一个字符串S的性质,而不是看论文时给人感觉的处理两个字符串。
应用最小表示法判断两个字符串同构,只要将两个串的最小表示求出来,然后从最小表示开始比较。剩下的工作就不用多说了。

根据上面的分析,不难写出代码。

代码:

#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cstdio>
using namespace std;
#define ms(a,b) memset(a,b,sizeof(a))
#define lson rt*2,l,(l+r)/2
#define rson rt*2+1,(l+r)/2+1,r
typedef unsigned long long ull;
typedef long long ll;
const int MAXN = 1e6 + 5;
const double EPS = 1e-8;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
char s[MAXN];
int n, nxt[MAXN];
void getnext() {
    nxt[0] = 0;
    for (int i = 1; i < n; i++) {
        int j = nxt[i - 1];
        while (s[i] != s[j] && j > 0)   j = nxt[j - 1];
        if (s[i] == s[j])   nxt[i] = j + 1;
        else    nxt[i] = 0;
    }
}
int getmin(){  
    int i=0,j=1,k=0;  
    while(i<n&&j<n&&k<n) {  
        int t=s[(i+k)%n]-s[(j+k)%n];  
        if(!t)  
            k++;  
        else {  
            if(t>0)  
                i+=k+1;  
            else  
                j+=k+1;  
            if(i==j)  
                j++;  
            k=0;  
        }  
    }  
    return min(i,j);  
}  

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

int main() {
    while (~scanf("%s", s)) {
        n = strlen(s);
        getnext();
        int mi = getmin(), mx = getmax();
        int cir = n - nxt[n - 1];
        int times = 1;
        if (n % cir == 0)   times = n / cir;
        printf("%d %d %d %d\n", mi + 1, times, mx + 1, times);
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值