Description
字母( \(Trie\) )树是一个表示一个字符串集合中所有字符串的前缀的数据结构,其有如下特征:
树的每一条边表示字母表中的一个字母
树根表示一个空的前缀
树上所有其他的节点都表示一个非空前缀,每一个节点表示的前缀为树根到该节点的路径上所有字母依次连接而成的字符串。
一个节点的所有出边(节点到儿子节点的边)中不存在重复的字母
现在 \(Matej\) 手上有 \(N\) 个英文小写字母组成的单词,他想知道,如果将这 \(N\) 个单词中的字母分别进行重新排列,形成的字母树的节点数最少是多少。
Input
第一行包含一个正整数 \(N\)( \(1\le N\le 16\) )
接下来 \(N\) 行每行一个单词,每个单词都由小写字母组成。
单词的总长度不超过 \(1000000\) 。
Output
输出仅一个正整数表示 \(N\) 个单词经过重新排列后,字母树的最少节点数。
Sample
Sample Input
3
a
ab
abc
Sample Output
4
Sample Input
3
a
ab
c
Sample Output
4
Sample Input
4
baab
abab
aabb
bbaa
Sample Output
5
Solution
显然一道题的正确做法不是它的题面,这道题的空间限制是 \(64 MB\) 想建一个 \(AC\) 自动机什么的老搞一搞的就别想了。
集合动规, \(f[S]\) 表示将集合 \(S\) 里的所有串重排后建 Trie 的最小节点数。转移是这样的 \[f[S] = min\{ f[k] + f[S \oplus k] \} - S中字符串公共部分长度\]
#include<bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for (int i = a; i <= b; i++)
int n, S, f[70000], g[200], cnt[20][200];
char s[1000001];
int main() {
scanf("%d", &n); S = (1 << n) - 1;
rep(i, 1, n) {
scanf("%s", s); int len = strlen(s);
rep(j, 0, len - 1) cnt[i][s[j]]++;
}
rep(i, 1, S) {
memset(g, 127, sizeof g); int sum = 0;
rep(j, 1, n) if (i & (1 << j - 1)) rep(k, 'a', 'z') f[i] += cnt[j][k], g[k] = min(g[k], cnt[j][k]);
rep(j, 'a', 'z') sum += g[j];
for (int j = i & i - 1; j; j = i & j - 1) f[i] = min(f[i], f[i ^ j] + f[j]);
if (f[i] > sum) f[i] -= sum;
}
printf("%d", f[S] + 1);
return 0;
}