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