Trie树 \color{green}{\texttt{Trie树}} Trie树
Trie树
是一种非常高效的字符串处理工具,它把字符串全部放在了一棵树上维护,而且通过一定的手段合并树上的节点,这使得它在空间复杂度和时间复杂度上都挺优秀的。
但是,有一个好的工具只是一个开始,重要的是应用,这里我来举一例。
UVA11488 Hyper Prefix Sets \color{green}{\texttt{UVA11488 Hyper Prefix Sets}} UVA11488 Hyper Prefix Sets
[Problem] \color{blue}{\texttt{[Problem]}} [Problem]
- 记 S S S 表示一个 01 01 01 字符串的集合。
- 记
f
(
S
)
=
S
f(S)=S
f(S)=S 内所有字符串的
LCP
(最长公共前缀)的长度 × S \times S ×S 内元素的个数。 - 输入 n n n 个 01 01 01 字符串,求出一个子集 T T T,使得 f ( T ) f(T) f(T) 最大。输出 f ( T ) f(T) f(T) 即可。
[Solution] \color{blue}{\texttt{[Solution]}} [Solution]
首先将所有的字符串建立一棵 Trie树
,我们考虑如何在 Trie树
上维护
f
(
T
)
f(T)
f(T) 的值。
看一个丑陋的示意图:
如果这是一棵 Trie树
,如何利用它求出最大的
f
(
T
)
f(T)
f(T) 呢?
首先,我们求出 Trie树
上每个节点的深度
dep
\texttt{dep}
dep 和有多少个节点经过了它(记为
cnt
\texttt{cnt}
cnt),像这样:
我们发现 Trie树
上每个节点
i
i
i 对答案的贡献就是
C
i
=
cnt
i
×
dep
i
C_i=\texttt{cnt}_{i} \times \texttt{dep}_i
Ci=cnti×depi,所以我们在建好 Trie树
后只需要对每个节点
i
i
i 都求一遍
C
i
C_i
Ci,再在所有的
C
i
C_i
Ci 中求出一个最大值即可。这样我们就可以获得 AC
了。
等等,还有一个问题不知道大家想过没有,题目不是说要 LCP
的长度吗?我们好像并不能保证每个节点所代表的前缀字符串都是若干个字符串的 LCP
吧 (如果不能理解这句话,那就多读几次吧,是有点拗口),那这样不会求出错误的解从而导致 WA
吗?
让我们来想想:什么样的节点不可能成为若干个字符串的 LCP
呢?仔细想想可以发现,只有一个儿子的节点不可能成为若干个字符串的 LCP
,如图(为了节约篇幅,我把竖着的树画横啦):
我们可不可以保证这些点的贡献不会影响答案呢?其实是可以的。因为它们只有一个儿子,所以它们的儿子一定还是这么多个字符串的前缀(即记该节点为 u u u,其儿子为 v v v,有 cnt u = cnt v \texttt{cnt}_u=\texttt{cnt}_v cntu=cntv),因为 dep v > dep u \texttt{dep}_v>\texttt{dep}_u depv>depu,所以 C v > C u C_v>C_u Cv>Cu,因此 u u u 考虑不考虑都不会影响答案。
[code] \color{blue}{\texttt{[code]}} [code]
const int N=1e7+100;
struct Trie_tree{
int nxt[N][2],tot,cnt[N],dep[N];
inline void init_trie(){
memset(nxt,-1,sizeof(nxt));
memset(cnt,0,sizeof(cnt));
memset(dep,0,sizeof(dep));
tot=0;//清空整一棵Trie树
}
void insert(char s[],int len){
register int u=0;//当前节点
for(int i=1;i<=len;i++){
register int c=s[i]-'0';
cnt[u]++;//u节点被经过了一次
if (nxt[u][c]==-1){
nxt[u][c]=++tot;
dep[nxt[u][c]]=dep[u]+1;
}//新建一个节点
u=nxt[u][c];//建立Trie树
}
cnt[u]++;//注意,最后一个点需要特别处理
}
inline int query(){
register int ans=0;
for(int i=1;i<=tot;i++)
ans=max(ans,dep[i]*cnt[i]);
return ans;
}
}Trie;char s[210];
int n,test_number;
int main(){
scanf("%d",&test_number);
while (test_number--){
scanf("%d",&n);
Trie.init_trie();
for(int i=1;i<=n;i++){
scanf("%s",s+1);
int len=strlen(s+1);
Trie.insert(s,len);
}
printf("%d\n",Trie.query());
}
return 0;
}
附:把一个数据结构封装到一个结构体内是一个很好的习惯哦。