1. 程式人生 > 實用技巧 >【Codeforces Global Round 7 D2】Prefix-Suffix Palindrome (Hard version)

【Codeforces Global Round 7 D2】Prefix-Suffix Palindrome (Hard version)

題目連結

連結

翻譯

讓你選擇字串 \(s\) 的一個字首和一個字尾(可以為空), 然後拼成一個字串。

要求這個字串得是一個迴文串,且這個字串的長度不能超過原串 \(s\) 的前提下最長。

輸出這個字串, hard 版本,長度小於等於 \(10^6\)

題解

接上文

現在的問題相當於要求從頭部開始的連續迴文串長 還是 以最後一個字元結尾的迴文串長。

分開兩步做,開頭連續的情況,則將 \(s\) 轉化為 s+"#"+reverse(s) 其中 reverse 為翻轉操作。

那麼現在相當於要找一個最大的數字 \(X\) 使得 \(s[1..X] == s[len-X+1,len]\)

這不就是 \(KMP\)

\(f\) 陣列嗎。。所以對新的 \(s\) 做一下 \(KMP\)\(f[len]\) 的值就是所求的 \(X\) 了。

然後把原始的 \(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;
}