题解 P7861 [COCI2015-2016#2] SAVEZ
模拟赛原题,赛时一直在肝另一题就没做
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++!}\)

浙公网安备 33010602011771号