【Codeforces Global Round 7 D2】Prefix-Suffix Palindrome (Hard version)
阿新 • • 發佈:2020-12-12
題目連結
翻譯
讓你選擇字串 \(s\) 的一個字首和一個字尾(可以為空), 然後拼成一個字串。
要求這個字串得是一個迴文串,且這個字串的長度不能超過原串 \(s\) 的前提下最長。
輸出這個字串, hard 版本,長度小於等於 \(10^6\)
題解
接上文
現在的問題相當於要求從頭部開始的連續迴文串長 還是 以最後一個字元結尾的迴文串長。
分開兩步做,開頭連續的情況,則將 \(s\) 轉化為 s+"#"+reverse(s)
其中 reverse
為翻轉操作。
那麼現在相當於要找一個最大的數字 \(X\) 使得 \(s[1..X] == s[len-X+1,len]\)
這不就是 \(KMP\)
然後把原始的 \(s\) 反向一下,再進行上述的井號拼接操作,求 \(f\) 同樣可以得到一個 \(X'\)
比較一下 \(X\) 和 \(X'\) 誰大,就說明頭部或尾部的迴文串比較長,對應輸出就好。
程式碼
#include <bits/stdc++.h> using namespace std; const int N = 1e6; int T; string s,pres,afters; int f[2*N+10]; int main(){ // freopen("C://1.cppSourceProgram//rush.txt","r",stdin); ios::sync_with_stdio(0),cin.tie(0); cin >> T; while (T--){ cin >> s; int l = 0,r = s.length()-1; while (l<=r && s[l] == s[r]){ l++;r--; } if (l > r){ cout << s << endl; continue; } pres =""; afters =""; if (0<l){ pres = s.substr(0,l); } if (r+1<(int)s.length()){ afters = s.substr(r+1); } if (l<r+1){ s = s.substr(l,r-l+1); }else{ cout << s << endl; continue; } string copyRawS = s; reverse(s.begin(),s.end()); s = copyRawS + "#" + s; int len = s.length(); //開始做 KMP f[0] = 0;f[1] = 0; int j; for (int i = 1;i < len; i++){ j = f[i]; while (j > 0 && s[i] != s[j]){ j = f[j]; } f[i+1] = j + (s[i]==s[j]?1:0); } //KMP 中 f[i+1]存的是以 i 這個字元結尾的字尾和字串的字首的最長公共字首。 //step1 求出前後最長公共字首1 int ma1 = f[len]; //然後求從最後一個字元開始的迴文串 //step 2: 先得到對應的井號形式字串 s = copyRawS; reverse(s.begin(),s.end()); s = s + "#" + copyRawS; //step 3: 同樣求出f陣列 f[0] = 0;f[1] = 0; for (int i = 1;i < len; i++){ j = f[i]; while (j > 0 && s[i]!=s[j]){ j = f[j]; } f[i+1] = j + (s[i]==s[j]?1:0); } //step 4: 求出前後最長公共字首2 int ma2 = f[len]; //step 5: 判斷從前面開始比較長 還是從後面開始比較長, 得到中間部分的迴文串。 string midAns; //如果中間沒有迴文 if (ma1 == 0 && ma1 == ma2){ midAns = ""; }else //前面比較長 if (ma1 > ma2){ midAns = copyRawS.substr(0,ma1); } //後面比較長 else{ int lenRaw = copyRawS.length(); midAns = copyRawS.substr(lenRaw-ma2,ma2); } //step 6 輸出三個部分答案 cout << (pres+midAns+afters) << endl; } return 0; }