bzoj 2740: 串

来源:互联网 发布:mac lnmp一键安装包 编辑:程序博客网 时间:2024/05/17 08:04

题目大意:给你一个串s[0..n-1],要你选两个数i,j,满足0<=i<=j<=n,将s[0..i-1],s[i..j-1],s[j..n-1]翻转之后的字典序最小

多组数据,保证n之和<=10000000

 

 

这题比较神,但是代码还是比较短的

首先先将s翻转一下,问题就转化为选i,j,使得将s[0..i-1] 与 s[j..n-1]的位置换一下后字典序最小。就是先找一个后缀j,将其放到最前面,再在剩下的部分找一个后缀i,将其放到剩下的这一部分的前面。我们可以先枚举j,然后对剩下的部分做最小表示法,不过时间复杂度是O(n^2)的,TLE不解释啊。然后我们发现,算法的瓶颈是在于枚举j,所以我们可以运用一种特殊的方法来求j。

具体来说,先做一遍最小表示法,求出T,再做一遍kmp,求出S[T..n]中最短的后缀等于前缀的串X,再求S的后缀中由X循环构成的最长的重复子串,该串就是后缀j,在对于剩下的部分一遍最小表示法就行了。

语文没学好,还是用具体的例子来说吧。

比如把S翻转后串是“acababab”,  那么最小表示法求出的T就是3,然后就是求“ababab” 中最短的后缀等于前缀的串,就是“ab”,再求其最长的由“ab”构成的后缀重复子串,就是“ababab”,所以j就是3,再对S[1..2]来一遍最小表示法就OK了。

证明:

设f(x)代表以x为j时,求出来的最小的字符串

S[i][j]代表以i开始,长度为j的串      S[I..j]代表以i开始,以j结束的串

T是S最小表示法求出来的开始点

一:对于任意 k<T    有f(k)>f(T) 

设len=n-T+1

如果S[k][len]<S[T][len] ,那么最小表示法求出的就不是T了,而是k

如果S[k][len]>S[T][len],那么把k这一段移到最前面比把T这一段移到最前面要大

如果S[k][len]==S[T][len]

Case1:len*2<=n-k+1有


其中a是s[1..K]范围内的最小表示法

将k所在的一段放到前面是 ABC    DaE,最小的变化就是 ABCaED….(1)

将T所在的一段放到前面是 C    DaEAB ,再将整个放到前面去,就是CDaEAB  ….(2)

因为有s[k][len]==s[T][len]所以A==C

所以比较(1)与(2) 的大小,就是比较BCaED  与 DaEAB的大小,

假设B是小于等于D的,那么最小表示法求出来的就是K而不是T了,所以B是大于D的

即BCaED>DaEAB   即(1)>(2) 所以得证。

Case 2:

len*2>n-k+1有


其中a是s[1..K]范围内的最小表示法

将k所在的一段放到前面是 ABC    DaE,最小的变化就是 ABCaED….(1)

将T所在的一段放到前面是 BC    DaEA ,再将其整个放到前面就是 BCDaEA…..(2)

因为有S[k][len]==S[T][len]所以AB==BC

所以比较(1)与(2) 的大小,就是比较CaED  与 DaEA的大小,

假设C是小于等于D的,那么最小表示求出来的就是K而不是T了,所以C是大于D的

即CaED>DaEA 即(1)>(2)  所以得证

 

 

 

二:有i,k,满足T<=i<=k<=n  , S[T..T+n-i]==S[i][n] , S[T..T+n-k]==S[k][n],且不存在一个j使得k<j<=n, S[T..T+n-j]==S[j][n],且S[i][n]不是S[k][n]循环若干次构成,   那么有f(i)>f(k)


若将i提前则是AB  P….(1)

若将K提前,则是AC  P…(2)

就只要证明(1)>(2)就OK了

因为后面是一样的,所以只要证明B >C即可

设A的长度是len      Bi代表把B前i*len个字符去掉的字符串    Ci代表把C前i*len个字符去掉的字符串

首先,根据图可知,C的最前方len个就是A,

如果S[i+n-k][len]>A那么得证

如果S[i+n-k][len]<A:

因为对于K,满足S[T..T+n-k]==S[k][n],所以S[T][len]=A,如果S[i+n-k][len]<A,那么最小表示法求出来就不是T,而是i+n-k了

如果S[i+n-k][len]==A

 我们就把B与C最前面同时消掉一个A,

一直这样递归下去就有


因为有S[i][n]不是S[k][n]循环若干次构成,所以最后会剩余Bn与Cn,长度小于len

当Bn<Cn时,那么T就不是最小表示法了,Bn才是

当Bn==Cn时,那么就存在j等于Bn,满足k<j<=n , S[T..T+n-j]==S[j][n]

当Bn>Cn时,得证

 

三:有i,k,满足T<=i<=k<=n  , S[T..T+n-i]==S[i][n] , S[T..T+n-k]==S[k][n],且不存在一个j使得k<j<=n, S[T..T+n-j]==S[j][n],且S[i][n]是S[k][n]循环若干次构成,   那么有f(i)<f(k)

 


把k提前后,那么下一次肯定会把i提前,用了两次,而如果直接把i提前,就达到了一样的效果,却省了一次操作。

所以得证。

 

 

 

 

综上所述,就是一遍最小表示法,一遍kmp,再一遍最小表示法就OK了


#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;const int maxn=10000011;char s[maxn];int n;void init(){scanf("%s",s+1);n=strlen(s+1);}int xx(char *s,int n){    int i,j,k;    for (i=1,j=2,k=0;i<=n && j<=n && k<n;++k){        if (s[i+k>n?i+k-n:i+k]>s[j+k>n?j+k-n:j+k]) i+=k+1,i+=(i==j),k=-1;        else if (s[i+k>n?i+k-n:i+k]<s[j+k>n?j+k-n:j+k]) j+=k+1,j+=(i==j),k=-1;    }    return min(i,j);}int next[maxn];void kmp(char *s,int n){    next[1]=0;    for (int i=2,j=0;i<=n;++i){        while (j && s[j+1]!=s[i]) j=next[j];        if (s[i]==s[j+1]) next[i]=++j;else next[i]=j;    }}void work(){    reverse(s+1,s+n+1);    int t=xx(s,n),x=n-t+1;kmp(s+(t-1),x);    while (next[x]) x=next[x];    int m=n-x+1,k,len=x;bool ok=1;    for (k=m-len;k>0 && ok;k-=len)        for (int i=0;i<len;++i) if (s[k+i]!=s[m+i]) ok=0;    m=k+len;if (!ok) m+=len;    printf("%d %d\n",n-m+1,n-xx(s,m-1)+1);}int main(){    int T=1;scanf("%d",&T);    while (T--) init(),work();    return 0;}


0 0
原创粉丝点击