最小表示法—介绍

最小表示法使得一个环形字符串有唯一的读法,这个读法是所有读法中字典序最小的。
本文讲解的是最小表示法的O(n)求法。
对于这种环形问题,一个常规的做法是把它自己复制一遍,接到原串之后。问题就转换成了求在字符串s中长度为n的最小子串。

先思考暴力做法。找到所有同构串,两两间比较一下,找到最小的。
优一点的做法,存下一个最小的同构串,逐一比较,如果有更小的则替换之。即便是这样时间复杂度O(n*|S|)。

想到这,其实已经命中了正解的一半。想要提速,最根本的办法就是减少无用状态空间的遍历
再仔细思考暴力的过程。假设在比较s[i~i+len]和s[j~j+len]中s[i+k]与s[j+k]不同(假设s[i+k]<s[j+k]),按暴力,我们仅仅得到s[i~i+len]小于s[j~j+len],然后舍弃j,选择i。深入地再想想,s[i+1~i+1+len]与s[j+1~j+1+len]的大小关系呢?因为s[i+1~i+k-1]与s[j+1~j+k-1]是相同的,而s[i+k]<s[j+k],所以s[i+1~i+1+len]小于s[j+1~j+1+len]。所以s[j+1~j+1+len]这个状态我们可以直接跳过。
像这样可以跳过的从s[j+1~j+1+len]一直到s[j+k~j+k+len]。我们可以对应的从s[i+1~i+1+len]到s[i+k~i+k+len]中找到比前者小的同构串。


举个例子,看图。
假设出现了(实际并不会这样匹配)i=1,j=4,k=2。现在s[i+k]>s[j+k],那么①>④。按照上面所说的,②、③都是可以直接跳过的,因为分别有⑤<②、⑥<③,②、③绝不肯能为最小表示法。所以i可以直接跳到i+k+1的位置。

实现时i、j就成了两个指针,表示同构串的起始位置。还要注意几处特殊的地方,看代码注释。

例题(bzoj2882 工艺)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=300010;

int n;
int a[2*maxn];//2倍空间

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        a[i+n]=a[i];
    }
    int i,j,k;
    for(i=1,j=2;i<=n && j<=n;)
    {
        for(k=0;k<n && a[i+k]==a[j+k];k++);//寻找不相同的位置 
        if(k==n) break;//由一个相同元素构成 
        if(a[i+k]<a[j+k])
        {
            j=j+k+1;
            if(i==j) j++;//i和j相同时有一个数要+1
        }
        if(a[i+k]>a[j+k])
        {
            i=i+k+1;
            if(i==j) i++;//i和j相同时有一个数要+1
        }
    }
    int begin=i>n?j:i;//未超出n的是最终胜者
    for(i=0;i<n;i++) printf("%d ",a[begin+i]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/A_Bright_CH/article/details/81506262