1. 程式人生 > 其它 >題解 P7861 [COCI2015-2016#2] SAVEZ

題解 P7861 [COCI2015-2016#2] SAVEZ

Trie優化dp

模擬賽原題,賽時一直在肝另一題就沒做

Solution

求最長子序列的題,顯然可以 \(\mathsf{dp}\),方程為 \(f_i=\max\limits_{x_j\,為\,x_i\,的字首和字尾}{f_j}+1\)

考慮怎麼判斷一個字串為另一個字串的的字首和字尾。我們有一個很妙的做法:把字串 \(s_{1\cdots n}\) 正反合在一起組成 \(n\)字元二元組,即 \((s_1,s_n)(s_2,s_{n-1})\dots(s_n,s_1)\),那麼這樣就只用判斷 \(x_j\) 組成的二元組是否是 \(x_i\) 的二元組的字首就好了。

對於這些二元組,我們可以直接建 \(\mathsf{Trie}\)

,把每個字串的二元組當成一個字符集為 \(26\times26\) 的字串插入,在插入的過程中如果當前節點是某個字串的結尾就直接進行 \(\mathsf{dp}\),並在插入完時的結點記錄此字串編號即可。

關於實現,由於這棵 \(\mathsf{Trie}\) 的每條邊有兩個字元 \(\texttt{ab}\),我們可以把它轉化為一個數 \(\texttt{(a-'A')*26+(b-'A')}\),並用 \(\mathsf{unordered\_map}\) 實現動態開點的子節點列表。

Code

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+5;
int n,m,ans,tot,f[N],id[N];char s[N];
unordered_map<int,unordered_map<int,int>>g;//Trie
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);m=strlen(s+1);
        int u=0;
        for(int j=1;j<=m;j++){
            f[i]=max(f[i],f[id[u]]+1);
            int w=(s[j]-'A')*26+s[m-j+1]-'A';//正反合並
            if(g[u].find(w)==g[u].end())g[u][w]=++tot;//動態開點
            u=g[u][w];
        }if(id[u])f[i]=max(f[i],f[id[u]]+1);
        ans=max(ans,f[i]);id[u]=i;
    }printf("%d\n",ans);
    return 0;
}

最後祝各位 \(\texttt{CSP2021 rp++!}\)