Minimum Palindromic Factorization(最少回文串分割)


Minimum Palindromic Factorization(最少回文串分割)

以下內容大部分(可以說除了關於回文樹的部分)來自論文A Subquadratic Algorithm for Minimum Palindromic Factorization

問題描述

給出一個字符串\(S\),將\(S\)划分為\(k\)個連續的字符串,使得每一個都是回文串,問\(k\)的最小值。

簡單做法

直接做法就是\(O(n^2)\)\(dp\),設\(PL[i]\)表示\(S[1..i]\)划分的最小值,集合\(P_i\)表示以\(i\)為結尾的回文串的開頭位置。那么

\[PL[j]=min_{i} \begin{Bmatrix} PL[i-1]+1 : i \in P_j \end{Bmatrix} \]

\(P_j\)可以由\(P_{j-1}\)推導出。總的時間復雜度為\(O(n^2)\).

更快的方法

如果\(P_j\)可以用另一個集合\(G_j\)代替,而\(G_j\)的大小只有\(O(logj)\),而且在\(O(logj)\)時間內就可以從\(G_{j-1}\)推導至\(G_j\),那么就有一個\(O(nlogn)\)的方法了。

定義1:對於一個字符串\(x\),如果\(y\)既是\(x\)的前綴,也是\(x\)的后綴,則\(y\)稱為\(x\)的一個邊界,如果\(y \neq x\),則\(y\)是一個真邊界。

引理1:假設\(y\)是回文串\(x\)的一個后綴,則\(y\)\(x\)的一個邊界當且僅當\(y\)是一個回文串。

證明:
顯然。

引理2:假設\(y\)\(x\)的一個邊界,且\(|x| \leq 2|y|\),則\(x\)是一個回文串當且僅當\(y\)是回文串。

證明:

(Figure 1)
(其中\(u^R\)表示\(u\)的翻轉)

定義2:假設一個字符串\(x\),如果存在一個長度為\(p(p \leq |x|)\)的字符串\(\omega\),滿足\(x\)\(\omega^{\infty}\)(無窮個\(\omega\)連接在一起)的一個字串,則\(p\)稱為\(x\)的一個階段。

顯然,\(y\)\(x\)的一個真邊界當且僅當\(|x|-|y|\)\(x\)的一個階段,一並考慮引理1,能得出引理3

引理3:\(y\)是回文串\(x\)的一個真后綴,則\(|x|-|y|\)\(x\)的一個階段當且僅當\(y\)是回文串。特別地,\(|x|-|y|\)\(x\)最小的階段當且僅當\(y\)\(x\)的最長的回文真后綴。

引理4:假設\(x\)是一個回文串,\(y\)\(x\)的最長回文真后綴,\(z\)\(y\)的最長回文真后綴,令\(x=uy, y=vz\),則

  1. \(|u| \geq |v|\)
  2. \(|u| > |v|\),則\(|u| > |z|\)
  3. \(|u| = |v|\),則\(u = v\)

證明

  1. 由引理\(3\)得,\(|u|=|x|-|y|\)\(x\)的最小階段,\(|v|=|y|-|z|\)\(y\)的最小階段。因為\(y\)\(x\)的子串,所以\(|u| > |y| > |v|\)\(|u|\)也是\(y\)的一個階段。前一個結論容易理解。當\(|u| \leq |y|\)時,由Figure 1可知重疊部分為回文串,且為\(y\)的真后綴,由引理\(3\)可知\(|u|\)是一個階段。
  2. 由引理\(1\)得,\(y\)\(x\)的邊界,\(v\)\(x\)的前綴,設\(x=v\omega\),則\(z\)\(\omega\)的邊界且\(|\omega|=|zu|(\because x=uvz=vzu)\),因假設\(|u|>|v|\),所以\(|\omega|>|y|\)。反證法:假設\(|u| \leq |z|\),則\(|\omega|=|zu| \leq 2|z|\),有引理\(2\)\(\omega\)是回文串,與\(y\)\(x\)的最長回文真后綴矛盾,得證。
  3. 由2可知\(v\)\(x\)的前綴,所以若\(|u|=|v|\),則\(u=v\)

利用上述引理可以得出關於\(P_j\)的一些特性,假設\(P_j=\begin{Bmatrix} p_1, p_2, ..., p_m \end{Bmatrix}, p_1<p_2<\cdots<p_m\)\(p_i-p_{i-1}\)稱為間隔。

引理5:\(P_j\)的間隔序列是不遞增的,而且最多有\(O(logj)\)個不同的間隔。

證明
\(\forall i \in [2..m-1]\)(\(m\)\(P_j\)的大小),設\(x=S[p_{i-1}..j], y=S[p_i..j], z=S[p_{i+1}..j]\),根據引理4,有間隔\(|u|,|v|\)。根據引理4的結論1,間隔序列是不遞增的。若\(|u|>|v|\),由引理4結論2得\(|x|>|u|+|z|>2|z|\),即回文后綴的長度會在兩步內變成一半,所以最多有\(O(logj)\)個不同的間隔。

\(P_j\)按間隔分成\(O(logj)\)個連續的子集,每個集合的間隔相等,即設\(P_{j, \Delta}=\begin{Bmatrix} p_i: 1 < i <m, p_i-p_{i-1}=\Delta \end{Bmatrix}, P_{j, \infty}=\begin{Bmatrix} p_1 \end{Bmatrix}\)。每一個\(P_{j, \Delta}\)用一個三元組表示\((min P_{j, \Delta}, \Delta, |P_{j, \Delta}|)\)。設\(G_j\)為一個鏈表,以\(\Delta\)遞減的順序存在這些三元組。
\(G_j\)的大小最大為\(O(logj)\),接下來會講如何在\(O(|G_{j-1}|)\)時間內將\(G_{j-1}\)轉移至\(G_j\)。在平方的算法中,需要對\(P_{j-1}\)的每個元素判斷是去掉還是替換為減一。以下的引理可證明這一決策對\(P_{j-1, \Delta}\)能同時操作。

引理6:\(p_i\)\(p_{i+1}\)\(P_{j-1, \Delta}\)的兩個連續的元素,則\(p_i-1 \in P_j\)當且僅當\(p_{i+1}-1 \in P_j\)

證明
根據定義,\(p_{i+1}-p_i=\Delta\),而且\(p_{i-1}=p_i-\Delta\)。根據引理4結論3,\(S[p_i-1]=S[p_{i+1}-1]=c\),所以\(p_i-1 \in P_j\)當且僅當\(S[j]=c\),即當且僅當\(p_{i+1}-1 \in P_j\)

所以每個三元組\((i, \Delta, k) \in G_{j-1}\)或是去掉或是用\((i-1, \Delta, k)\)。即

\[G'_j=\begin{Bmatrix} (i-1, \Delta, k):(i, \Delta, k) \in G_{j-1}, i>1, and S[i-1]=S[j] \end{Bmatrix} \]

但是\(G'_j\)可能不滿足定義,因為某些間隔改變了。准確的說,當\(P_{j-1, \Delta}\)的最小元素\(p_i\)替換成了\(p_i-1\),但\(p_{i-1}=p_i-\Delta\)被去掉了(因為\(p_{i-1}\)不符合引理4結論3,有可能\(S[p_{i-1}-1] \neq S[j]\)),則\(p_i-1\)不再屬於\(P_{j, \Delta}\)。這時需要把\(p_i-1\)單獨拆分,即將\((p_i-1, \Delta, k)\)分成\((p_i-1, \Delta', 1)\)\((p_i-1+\Delta, \Delta, k-1)\)(如果\(k>1\)),其中\(\Delta'\)為新的間隔。在這過程中還需要合並相同間隔的三元組。(詳細可看最后的代碼)

引理7:\(G_j\)能在\(O(|G_{j-1}|)=O(logj)\)時間內從\(G_{j-1}\)推導出。

接下來說明如何在\(O(|G_j|)\)的時間內,利用\(PL[j-1], G_j\)推導出\(PL[j]\)

引理8:\((i, \Delta, k) \in G_j, k \geq 2)\),則\((i, \Delta, k-1) \in G_{j-\Delta}\)

證明
根據定義,\((i, \Delta, k) \in G_j\)等價於\(P_{j, \Delta}=\begin{Bmatrix} i, i+\Delta, ..., i+(k-1)\Delta \end{Bmatrix}\),現需要證明\(P_{j-\Delta, \Delta}=\begin{Bmatrix} i, i+\Delta, ..., i+(k-2)\Delta \end{Bmatrix}\)。現在先證明\(P_{j-\Delta, \Delta} \cap [i-\Delta+1..j-\Delta]=\begin{Bmatrix} i, i+\Delta, ..., i+(k-2)\Delta \end{Bmatrix}\),而且\(P_{j-\Delta, \Delta} \cap [1..i-\Delta]=\varnothing\)

因為\(y=S[i..j], x=S[i-\Delta..j]\)(根據\(\Delta\)的定義)都是回文串,而且\(y\)\(x\)最長的真邊界,\(S[i-\Delta..j-\Delta]=y=S[i..j]\)。所以對於\(\forall l \in [i..j], l \in P_j\)當且僅當\(l-\Delta \in P_{j-\Delta}\)。特別地,因為間隔相同,所以\(\forall l \in [i+1..j], l \in P_{j, \Delta}\)當且僅當\(l-\Delta \in P_{j-\Delta, \Delta}\)。所以\(P_{j-\Delta, \Delta} \cap [i-\Delta+1..j-\Delta]=\begin{Bmatrix} i, i+\Delta, ..., i+(k-2)\Delta \end{Bmatrix}\)

現在還需證明\(P_{j-\Delta, \Delta} \cap [1..i-\Delta]=\varnothing\)。這為真當且僅當\(i-2\Delta \notin P_{j-\Delta}\)。反證法:\(S[i-2\Delta..j-\Delta]\)是回文串,設\(\omega=S[i-2\Delta..i-\Delta-1]\),則\(S[j-2\Delta+1..j-\Delta]=\omega^R\)。因為\(z=S[i-\Delta..j-\Delta]\)\(S[i-\Delta..j]\)都是回文串,所以\(S[i-\Delta..i-1]=\omega, S[j-\Delta+1..j]=\omega^R\)。因為\(z\)是回文串,所以\(S[i-2\Delta..j]=\omega z \omega^R\)也是回文串,所以\(i-2\Delta \in P_j, i-\Delta \in P_{j, \Delta}\),矛盾,得證。而且\(i-2\Delta \notin P_{j-\Delta}\)一定成立,若\(i-2\Delta in P_{j-\Delta}\),則\(P_{j-Delta, \Delta}\)的最小元素不是\(i\)

所以\(P_{j, \Delta}=P_{j-\Delta, \Delta} \cup max P_{j, \Delta}\)(當\(|P_{j, \Delta} \geq 2\))。這樣\(PL_{j, \Delta}=min{PL[i-1]+1 : i \in P_{j, \Delta}}\)就能利用\(PL_{j-\Delta, \Delta}\)在常數時間內得出。
\(GPL[i]\)\(GPL[m=min(P_{j, \Delta}-\Delta]=PL_{j, \Delta}\),注意到\(PL_{j-\Delta, \Delta}\)也是存在\(m\)這個位置(若\(|P_{j, \Delta} \geq 2\))。以下引理證明位置\(m\)\((j-\Delta..j)\)不會被其它數重寫。

引理9:\(m=min(P_{j, \Delta}-\Delta), \forall l \in [j-\Delta+1..j-1], m \notin P_l\)

證明
反證法:假設\(m \in P_l, l \in [j-\Delta+1..j-1]\),則\(S[m..l]\)是回文串,則\(S[m+h..l-h], h=l-j+\Delta\)也是回文串。因為\(l-h=j-\Delta, m<m+h<m+\Delta=min(P_{j-\Delta, \Delta})\),所以\(m+h\)才是\(min(P_{j-\Delta, \Delta})\)\(P_{j-\Delta}\)中的前一個,\(min(P_{j-\Delta, \Delta}) \notin P_{j-\Delta, \Delta}\),矛盾,得證。

定理:將一個長度為\(n\)的字符串分解成最少回文串可以在時間復雜度為\(O(nlogn)\),空間復雜度為\(O(n)\)下算出。

/*
tripe{nid, delta, sum}(開頭位置, 間隔, 個數)
*/
    memset(PL, 0x7f, sizeof PL);
    memset(GPL, 0x7f, sizeof GPL);
    PL[0]=0;    
    vtri G;
    G.clear();
    for (int j=1; j<=n; ++j)
    {
        vtri h;
        h.clear();
        int r=-j; //前者結尾位置
        for (auto &i : G)
            if (i.nid>1 && st[i.nid-1]==st[j])
            {
                int nid=i.nid-1;
                if (nid-r!=i.delta) //間隔不同
                {
                    //拆分
                    h.push_back(tripe{nid, nid-r, 1});
                    if (i.sum>1)
                        h.push_back(tripe{nid+i.delta, i.delta, i.sum-1});
                }
                else h.push_back(tripe{nid, i.delta, i.sum}); //間隔相同
                r=nid+(i.sum-1)*i.delta; //更新前者結尾位置
            }

        if (j>1 && st[j-1]==st[j]) //長度為2的回文串
        {
            h.push_back(tripe{j-1, j-1-r, 1});
            r=j-1;
        }
        h.push_back(tripe{j, j-r, 1}); //長度為2的回文串
        G.clear();

        //合並相同間隔的三元組
        G.push_back(*h.begin());
        for (vtri::iterator it=h.begin()+1; it!=h.end(); ++it)
            if (G.back().delta==(*it).delta) G.back().sum+=(*it).sum;
            else G.push_back(*it);

        PL[j]=j;
        for (auto &i:G)
        {
            int r=i.nid+(i.sum-1)*i.delta;
            int m=PL[r-1]+1;
            if (i.sum>1) m=min(m, GPL[i.nid-i.delta]);
            if (i.delta<=i.nid) GPL[i.nid-i.delta]=m;
            PL[j]=m;
        }
    }
    }

與回文樹的結合

回文樹可以維護以某個點為結尾的回文串,而且回文樹中的\(fail[i]\)指向的是長度僅次於\(i\)的回文串的回文串。也就是說沿着\(fail\)走到\(root\)得到的路徑就是以\(i\)為結尾的回文串,而且設\(diff\)為路徑中相鄰兩個點的\(len\)的差,則\(diff\)就是間隔序列,而且這個間隔序列是滿足上述的性質的,所以可以另外設一個數組\(anc\)來維護\(i.nid\)(同一個\(\Delta\)的開頭位置)的位置,時間復雜度也是\(O(nlogn)\)

void init() //回文樹初始化
{
    S[0]=-1;
    m=0;       //字符串長度
    total=1;   //回文樹點數
    last=0;    //最后插入的點
    len[0]=0;  //回文串長度
    len[1]=-1;
    fail[0]=fail[1]=1;
}
void insert(int ch)
{
    S[++m]=ch;
    int cur=last;
    while (S[m-len[cur]-1]!=S[m]) cur=fail[cur];
    if (!son[cur][ch])
    {
        len[++total]=len[cur]+2;
        int tmp=fail[cur];
        while (S[m-len[tmp]-1]!=S[m]) tmp=fail[tmp];
        tmp=son[tmp][ch];
        fail[total]=tmp; son[cur][ch]=total;
        diff[total]=len[total]-len[tmp]; //間隔序列
        anc[total]=(diff[total]==diff[tmp]? anc[tmp]:tmp); //開頭位置
    }
    last=son[cur][ch];
}
void solve()
{
    init();
    for (int i=1; i<=n; ++i) ans[i]=inf;
    for (int i=1; i<=n; ++i)
    {
        insert(a[i]);
        for (int j=last; j; j=anc[j])
        {
            hd[j]=i-len[anc[j]]-diff[j]; //GPL存放位置
            if (anc[j]!=fail[j] && ans[hd[fail[j]]]<ans[hd[j]])
                hd[j]=hd[fail[j]];
            if (!(i & 1) && ans[hd[j]]+1<ans[i]) ans[i]=ans[hd[j]]+1;
        }
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM