问题描述:
字母(Trie)树是一个表示一个字符串集合中所有字符串的前缀的数据结构,其有如下特征:
1.树的每一条边表示字母表中的一个字母
2.树根表示一个空的前缀
3.树上所有其他的节点都表示一个非空前缀,每一个节点表示的前缀为树根到该节点的路径上所有字母依次连接而成的字符串。
4.一个节点的所有出边(节点到儿子节点的边)中不存在重复的字母。
现在Matej手上有N个英文小写字母组成的单词,他想知道,如果将这N个单词中的字母分别进行重新排列,形成的字母树的节点数最少是多少。
输入格式:
第一行包含一个正整数N(1<=N<=16)
接下来N行每行一个单词,每个单词都由小写字母组成。
单词的总长度不超过1,000,000。
输出格式
输出仅一个正整数表示N个单词经过重新排列后,字母树的最少节点数。
样例输入
10
jgda
dbfdjj
hehegdfh
faeejic
acagdgfcjc
jifiigdbif
fdbdii
ch
c
adccdd
样例输出
42
题解:
首先考虑到两个字传的最优解为两者长度之和减去其两者公共串。
但推广到三者时此方法不成立,但可将其转换为两两组合后在减去其公共部分,进而可想到dp,又因为n<16,考虑状态压缩。
f[i]=min(f[i],f[i]&f[i^j]-sum[i]);
注意 for(j=i&i-1;j;j=i&j-1) 这句精妙高效的遍历子集的方法
代码
#include<stdio.h>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define inf 70005
#define maxn 1000005
int n,cnt[27][205],a[205];
int f[inf];
char s[maxn];
int k;
int sum;
int main()
{
int i,j,k;
int a1;
scanf("%d",&n);
k=(1<<n)-1;
for(i=1;i<=n;i++)
{
scanf("%s",&s);
int t=strlen(s);
for(j=0;j<t;j++) cnt[i][s[j]]++;
}
for(i=1;i<=k;i++){
memset(a,60,sizeof(a));
sum=0;
for(j=1;j<=n;j++)
{
if(i&(1<<j-1)){
for(a1='a';a1<='z';a1++)
{
f[i]+=cnt[j][a1];
a[a1]=min(a[a1],cnt[j][a1]);
}
}
}
for(j='a';j<='z';j++) sum+=a[j];
for(j=i&i-1;j;j=i&j-1) f[i]=min(f[i],f[j^i]+f[j]);
if(f[i]-sum>0) f[i]=f[i]-sum;
}
cout<<f[k]+1;
}