题解【P6289 [COCI2016-2017#1] Vještica】


传送门。

$\texttt{Description}$

给定 $n$ 个字符串,你可以将任意个字符串重组,使得存储这些单词的 $\text{Trie}$ 数的节点数尽量小。$1\le n\le 16$。

$\texttt{Solution}$

第一眼发现 $n\le 16$,可以考虑状压 $\texttt{DP}$。

我们设 $f[s]$ 表示插入完集合 $s$ 中的所有字符串的最小节点数。

那么显然答案是 $f[S]$,$S$ 表示所有字符串的集合。

考虑转移。

先解决一种简单的情况,假设说只有两个字符串 $A,B$,那么将它们插入到 $\text{Trie}$ 树上后所需的节点数为 $len_A+len_B-\text{same}(A,B)$,$\text{same}(A,B)$ 指 $A$ 和 $B$ 可能的最长公共前缀。

这种简单的情况是可以推广到 $n$ 个字符串的。

对于 $f[s]$,我们考虑枚举 $s$ 的子集 $s'$,那么 $s\operatorname{xor} s'$ 是 $s$ 的另一个子集 $s''$。

我们直接对于 $s'$ 和 $s''$ 看做两个字符串,长度分别为 $f[s']$ 和 $f[s'']$,所以就可以归结为两个字符串的情况。

于是得到了最终的状态转移方程:$f[s]=\min_{s'\in s}\{f[s']+f[s\operatorname{xor} s']-\operatorname{same}(s)\}$。

考虑函数 $\operatorname{same}$ 如何求。

我们贪心的想,对于一个字符 $c$,我们考虑集合中所有字符串中 $c$ 的数量,找到最小的那一个,记为 $x$,是不是说明公共的长度又能减少 $x$,因为所有的字符串都有这 $x$ 个字符 $c$。我们对于每一个字符都求一遍相应的 $x$,求和,便是 $\operatorname{same}(s)$ 的值。

细节留给读者自行处理。

贴一下代码:

#include
#include
using namespace std;
const int MAXN(18);
const int MAXM(1e6+10);
const int INF(1e9+10);
int n;
char s[MAXN][MAXM]; 
int fac[MAXN],len[MAXN];
inline int lowbit(int x){return x&(-x);}
inline int Min(int x,int y){return x

$$\texttt{The End.by UF}$$