6902: Trie树
时间限制: 1 Sec 内存限制: 128 MB
提交: 176 解决: 37
[提交] [状态] [讨论版] [命题人:admin]
题目描述
字母(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
来源/分类
题解:状压DP+字符串,从小到大枚举单词的所有集合情况,然后枚举集合的子集与子集的补集合并后减去公共前缀的总长度就是该集合的最优情况,最后枚举到n个集合即可。学习到了一些状压dp位运算枚举的小技巧呀
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e7+1005;
typedef long long ll;
char s[maxn];
int cnt[30][30],dp[1<<17];
int c[30];
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%s",s);
int len=strlen(s);
for(int j=0;j<len;j++) //统计每个单词中字母的个数
cnt[i][s[j]-'a']++;
}
for(int i=0;i<(1<<n);i++) //枚举所有单词的集合
{
dp[i]=0;
memset(c,0x3f,sizeof(c));
for(int j=0;j<n;j++)
if((1<<j)&i) //枚举集合中第几个单词存在
{
for(int ch=0;ch<26;ch++)
{
dp[i]+=cnt[j][ch]; //统计当前集合所有字母数,这有可能是最坏情况,没有公共前缀
c[ch]=min(c[ch],cnt[j][ch]); //统计每个字母的最小出现次数,若不为0,则可以作为公共前缀
}
}
int sum=0;
for(int j=0;j<26;j++) sum+=c[j];
for(int j=i&i-1;j;j=i&j-1) //j是i的一个子集,j^i是j的补集,合并后可减掉公共前缀
dp[i]=min(dp[i],dp[j]+dp[j^i]-sum);
}
printf("%d\n",dp[(1<<n)-1]+1);
return 0;
}