bzoj 2740: 串

題目大意:給你一個串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;
}


發佈了43 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章