题目描述
字母(Trie)树是一个表示一个字符串集合中所有字符串的前缀的数据结构,其有如下特征:
1.树的每一条边表示字母表中的一个字母
2.树根表示一个空的前缀
3.树上所有其他的节点都表示一个非空前缀,每一个节点表示的前缀为树
根到该节点的路径上所有字母依次连接而成的字符串。
4.一个节点的所有出边(节点到儿子节点的边)中不存在重复的字母。
单词“A”“to”“tea”“ted”“ten”“i”“in”“inn”对应的Trie树
现在Matej手上有N个英文小写字母组成的单词,他想知道,如果将这N个单词中的字母分别进行重新排列,形成的字母树的节点数最少是多少。
输入
第一行包含一个正整数N(1<=N<=16)
接下来N行每行一个单词,每个单词都由小写字母组成。
单词的总长度不超过1,000,000。
输出
输出仅一个正整数表示N个单词经过重新排列后,字母树的最少节点数。
样例输入
3 a ab abc
样例输出
4
[思路]
状态压缩.
对于两个串, aaabb 与 aabccc 打乱内部顺序, 只需考虑字母相同个数即可, 最少节点数 = 两串长度和-公共串
= 5+6-3=8
然而当串的个数大于2个时,这个结论是不成立的.发现N 只有16, 可以采用状态压缩的思路,把串两两分组,分组的个数即为2^n 个. 这样分下去是可行的, 用0 代表不选,1 代表选择, 那么 最终的答案就是 1111111 ..1 n个1 中 然后加上空节点一个.
然后 学习到了 骚操作--: for(int j=i&i-1;j;j= i&j-1) 遍历所有的子集
[代码]
#include <iostream>
#include <bits/stdc++.h>
#define rep(i,a,n) for(int i=a;i<n;i++)
#define per(i,a,n) for(int i=n-1;i>=a;i--)
typedef long long ll;
using namespace std;
const int mod= 1e9+7;
const int maxn = 1e6+10;
char str[maxn];
int dp[1<<17];
int c[30];
int cont[50][50];
int len[50];
int n;
int main()
{
scanf("%d",&n);
rep(i,0,n)
{
scanf("%s",str);
len[i] = strlen(str);
rep(j,0,len[i]) // count
cont[i][str[j]-'a']++;
}
rep(i,0,1<<n)
{
dp[i] = 0;
memset(c,0x3f,sizeof(c));
rep(j,0,n)
{
if( (1<<j)&i )// i 集合内 的所有单词
{
dp[i]+=len[j];
rep(k,0,26)
{
//dp[i] += cont[j][k]; // len
c[k] = min(c[k],cont[j][k]); // 统计 最小的字母个数
}
}
}
int sum = 0;
rep(k,0,26) sum+=c[k];// 最小公共前缀
for(int j=i&i-1;j;j= i&j-1)//骚操作之 遍历 所有的子集
{
// cout<<dp[i]<<" "<<(dp[j^i]+dp[j])<<" "<<sum<<endl;
dp[i] = min(dp[i],dp[j^i]+dp[j]-sum); // 两个字串的长度 - 公共字串
//cout<<i<<" "<<j<<" "<<(j^i)<<endl;
}
}
printf("%d\n",dp[(1<<n)-1]+1);// + 空节点
return 0;
}
/*
5
aaabbab
abcd
cdeag
abgf
aabb
*/