原题
题面简化
有n个字符合集,求其前缀树的最少结点个数。
数据范围
n ≤ 16,1 ≤ m ≤1000000(字符个数)
思路
首先看到
n ≤ 16
可知,这道题是状压DP或者暴力
状态是什么?
因为是n的数据范围小,n为字符合集个数,所以状态是 n个字符合集的整体 的选择情况,
这里用S表示,二进制范围为 1~1111111111111111。
怎么进行转移?
因为目标值是前缀树的最少结点个数,
所以可以把这棵树拆为两颗进行合并,这也是树形DP的常见思路。
怎么拆?
因为这颗前缀树在节点数最少的条件下,一种结构就可能由不同状态转移而来,
所以,须枚举所有选择情况。
怎么合?
因为我们枚举了所有选择情况,其中一定有可转移为最优结构的选择,
所以我们直接把两棵树都连上根,把前缀树的公共前缀合并,
即 减去现在所有选择的字符合集的交集的字符个数。
代码实现
#include<bits/stdc++.h>
using namespace std;
int n,_[16][26],cnt[17][26],dp[1<<16];
char a[16][1000006];
int LCP(int s){
int len=0;
memset(_[n],0x3f,sizeof(_[n]));
for(int i=0;i<n;i++)
if(s&(1<<i)){
for(int k=0;k<26;k++)
_[n][k]=min(_[n][k],_[i][k]);
}
for(int k=0;k<26;k++)len+=_[n][k];
return len;
}
int dfs(int s){
int &ret=dp[s];
if(ret!=-1)return ret;
int lcp=LCP(s);
if((s&(-s))==s)return ret=lcp;
ret=100000000;
for(int pS=(s-1)&s;pS>0;pS=(pS-1)&s)
ret=min(ret,dfs(pS)+dfs(s^pS)-lcp);
return ret;
}
int main(){
memset(dp,-1,sizeof(dp));
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%s",a[i]);
for(int j=0;a[i][j];j++){
_[i][a[i][j]-'a']++;
}
}
printf("%d",dfs((1<<n)-1)+1);
}