字符串循环同构——最小表示法の板子

来源:互联网 发布:linux服务端口 编辑:程序博客网 时间:2024/05/21 14:56

字符串的最小表示

一个字符串S通过循环移位能够得到一些别的字符串,比如“abcde”循环一次可以得到“bcdea”,循环两次得到“cdeab”,等等。这些串当中字典序最小的那个就叫做S的最小表示。

求出字符串的最小表示可以方便地判断两个串是否循环同构。

某些别的求解方法

求最小表示或者循环同构实际上有很多别的方法的。。。并且渐进时间复杂度达到理论下界的O(n)的做法也有很多。。

比如说直接求最小表示的话可以把字符串在后面接一遍然后搞一个后缀自动机每次顺着最小的边走n步就可以了呀~
但是常数巨大空间巨大。。。(逃

如果求循环同构的话把第一个串在后面接一遍然后第二个串跑KMP就可以了呀~
其实感觉这种做法的理论复杂度和常数都不比最小表示法啥的差。。

最小表示法

参考2003年集训队论文 周源《浅析“最小表示法”思想 在字符串循环同构问题中的应用》

设一个字符串的函数M(S)返回的是从S的第M(S)个字符开始引起的S一个循环表示是S的最小表示。
当要判断两个长度为n的串S和T是否循环同构的时候,首先把S和T都在后面接一遍,然后设两个指针p1和p2分别指向S和T中的字符。当p1=M(S)并且p2=M(T)的时候,如果S和T是循环同构的,那么一定可以得到从p1和p2向后匹配n个位置都是相等的。

最小表示法求字符串循环同构的基本思路就是假设它们是循环同构的,然后去寻找可以成功向后匹配n个字符的位置,如果找不到就说说明它们不是循环同构的。接下来的讨论中假设S和T是循环同构的。
在找M(S)和M(T)的时候,因为我们是从前往后找的,那么只要我们能保证在这个时刻有p1<M(S)并且p2<M(T),就还有机会通过后移指针来找到M(S)和M(T)。

首先令p1和p2都指到第一个字符,显然这个时候一定有p1<M(S)并且p2<M(T)
然后从这两个位置开始往后匹配,如果成功匹配了n个位置就说明S和T循环同构,返回p1和p2中位置比较靠前的那一个。
但是如果不能成功匹配n个位置,就是说一定有一个位置走着走着不相等了。假设这个时候S(p1+k)>T(p2+k),其他情况是类似讨论的。
现在字符串是这个样子:
这里写图片描述

假如p1已经指到了M(S)这个位置,那么p1往后延伸n个字符形成的字符串一定不大于T的任何一个位置向后延伸n个字符形成的字符串。这个比较好理解,因为M(S)向后n个字符已经是最小字典序的了,找不到比它更小的了。
那么对于p1…p1+k这些位置来说,以它们中的某一个开头,向后延伸n个位置形成的字符串,一定可以在T中找到一个字符串字典序小于它。因为比如找到的那个位置是p1+x,那么只要对应在T中从p2+x开始比较,首先S(p1+x..p1+k-1)和T(p2+x..p2+k-1)都是相等的,比较到p1+k和p2+k这两个位置的时候一定会判断S(p1+k)>S(p2+k)。
这说明p1..p1+k这些位置都不可能成为M(S)。那么我们可以直接把指针移动到p1+k+1。
因为p1和p2某一个指针向后移动n个位置以后一定能找到一个可以判同构的位置,所以这个算法的时间复杂度是线性的。

这里有一个问题就是当我们在求两个串的循环同构的时候返回的p1和p2实际上不一定是M(S)和M(T),因为如果两个串本来就是相等的,它从第一个位置开始跑n个就发现相等然后返回了,但是第一个位置不一定是M(S)或者M(T)。比如“edcba”和“edcba”两个相同的串。最小表示应该是“aedcb”,而如果直接从两个串的开头比较的话会直接返回第一个字符的位置。
为了能够正确求出M(S)和M(T),我们做一个处理就是不要让p1和p2指到同一个位置。当两个指针指到同一个位置的时候把某一个指针后移一位。这样就能避免上面提到的这种情况从而使得求出来的一定是正确的M(S)和M(T)了。

当题目要求的不是两个串是否循环同构而是求字符串的最小表示的时候是一样的,就让p1和p2都指在当前字符串上,一个在0号位置一个在1号位置就可以了。

贴代码:

int MinRep(){    int i,j,k;    i=1;j=2;k=0;    while (i<=n&&j<=n){        k=0;        while (v[i+k]==v[j+k]&&k<=n) ++k;        if (k==n+1) break;        if (v[i+k]>v[j+k])          if (i+k<=j) i=j+1;          else i=i+k+1;        else          if (j+k<=i) j=i+1;          else j=j+k+1;        if (i==j) ++j;    }    return min(i,j);}

这里有一个优化就是当指针i要跳到p1+k+1的时候,如果p1+k+1比p2的位置要靠前,那么直接跳到p2+1就可以了,因为p1+k+1..p2这些位置会被p2指针检查到,所以是正确的。

扯了这么一大篇结果代码只有这么一点点= =
BZOJ2882可以当做一个板子题。

0 0
原创粉丝点击