现在的位置: 首页 > 综合 > 正文

【字符串循环同构的最小表示法】

2013年08月04日 ⁄ 综合 ⁄ 共 1376字 ⁄ 字号 评论关闭

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

对于一个字符串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的性质,而不是看论文时给人感觉的处理两个字符串。
应用最小表示法判断两个字符串同构,只要将两个串的最小表示求出来,然后从最小表示开始比较。剩下的工作就不用多说了。

int Minrp(char *s, int l){//s是要处理的字符串,l是字符串s长度
    int i = 0, j = 1, k = 0, t;
    while(i < l && j < l && k < l) {
        t = s[(i+k) >= l ? i+k-l : i+k] - s[(j+k) >= l ? j+k-l : j+k];
        if(!t) k++;
        else{
            if(t > 0) i = i + k + 1;//最大表示的话只要把t>0 改为t<0即可
            else j = j + k + 1;
            if(i == j) j++;
            k = 0;
        }
    }
    return i;
}

附POJ 1509模板,跟ZOJ 1729差不多

#define N 10005
char str[N];
int Minrp(char *s,int l){
    int i=0,j=1,k=0,t;
    while(i<l && j<l && k<l){
        t = s[(i+k) >= l ? i+k-l : i+k] - s[(j+k) >= l ? j+k-l : j+k];
        if(!t)k++;
        else {
            if(t>0)i = i+k+1;
            else j = j+k+1;
            if(i==j)j++;
            k = 0;
        }
    }
    return i;
}
int main(){
    int n;
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%s",str);
        printf("%d\n",Minrp(str,strlen(str))+1);
    }
    return 0;
}

抱歉!评论已关闭.