题目
分析
既然每个单词的字母可以随意调换位置,那么字母的位置就无所谓了,一个单词仅由各个字母的数量决定。发现 n ≤ 16 n\leq 16 n≤16,考虑状压DP。
集合 S S S表示当前这颗树上选了哪些单词, d p [ S ] dp[S] dp[S]表示最优字典树的结点个数, d p [ S ] dp[S] dp[S]可以由每一个 d p [ A ] dp[A] dp[A]( A ⊂ S A\subset S A⊂S)和 d p [ ∁ S A ] dp\left[\complement_SA\right] dp[∁SA]转移,即合并这两棵字典树。
考虑集合
A
A
A中每个单词中各个字母出现的最少次数
m
i
(
0
≤
i
≤
25
)
m_i(0\leq i\leq25)
mi(0≤i≤25),则这棵树大概这个样子的:
但是可能每个分支间还能合并,可参见样例一,所以根据这一点DP即可。
(这是个人理解,如与读者有出入,或无法理解,评论区欢迎您)
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 16
#define INF 0x3f3f3f3f
int N;
int cnt[MAXN+5][30];
int dp[(1<<MAXN)+5],Min[30];
int main(){
freopen("vjestica.in" ,"r", stdin);
freopen("vjestica.out","w",stdout);
scanf("%d",&N);
getchar();
for(int i=1;i<=N;i++){
char c=getchar();
while(c!='\n'){
cnt[i][c-'a']++;
dp[1<<(i-1)]++;
c=getchar();
}
}
for(int S=3;S<=(1<<N)-1;S++){
memset(Min,0x3f,sizeof Min);
for(int i=0;i<N;i++)
if((S>>i)&1)
for(int j=0;j<26;j++)
Min[j]=min(Min[j],cnt[i+1][j]);//找到m
int tmp=0;
for(int i=0;i<26;i++)
tmp+=Min[i];//统计有多少可以合并
dp[S]=min(dp[S]?dp[S]:INF,INF);
for(int i=1;i<S;i++){
if((i|S)==S){
int j=S^i;
dp[S]=min(dp[S],dp[i]+dp[j]-tmp);
//由上图可知,要合并导致减少的结点和i、j都无关
}
}
}
printf("%d",dp[(1<<N)-1]+1);
}