2020.08.14日常总结——Trie树的实际应用

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;
}

附:把一个数据结构封装到一个结构体内是一个很好的习惯哦。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值