首页 > 学院 > 开发设计 > 正文

jzoj 5001. 【NOI2017模拟3.4】Trie树 状压dp

2019-11-06 06:28:11
字体:
来源:转载
供稿:网友

题意

给出n个字符串,每个字符串的字符可以任意调换位置,问将这些字符串建一棵trie树需要的最少节点数(包括根节点)。 n<=16,字符串总长<=10000000

分析

咋一看啥思路都没有,其实并不难,说白了还是我太弱啦。。。 复制一波题解: 显然,如果我们希望 Trie 树的节点数尽量少,我们应该先将所有单词公共的字母拿出 来,作为 Trie 树最上几层的初始链。比如说我们有 aaab,baab 和 cab 三个单词,我们会将 ab 挑出来,然后剩下的单词就变成了 aa,ab,c。 对于剩下的单词,我们将其分成两个子集,(aa,ab)和(c),并分别再计算最长的公 共字母链。显然,当集合中有 n 个单词时,有 2 n种方式将这些单词分成两个子集。 由此,我们可以用状态压缩 dp 解决这个问题。一个状态由单词的子集来描述,也就是 说我们有 2 n个状态,并计算每一种子集形成 Trie 树需要的最少节点数,转移时枚举如何将 子集分裂成两个更小的子集,即可解决整个问题。整个算法总的时间复杂度为 O(3n)

顺带发一波枚举i的子集的方法:for (int j=i;j;j=(j-1)&i); 这样就可以枚举所有i的子集啦。

代码

#include<iostream>#include<cstdio>#include<cstdlib>#include<cstring>#include<algorithm>#define N 35#define inf 0x3f3f3f3fusing namespace std;int bin[N],n,len[N],s[N][30],f[1048580],mn[30];char str[1000005];int main(){ freopen("trie.in","r",stdin);freopen("trie.out","w",stdout); bin[0]=1; for (int i=1;i<=16;i++) bin[i]=bin[i-1]*2; scanf("%d",&n); for (int i=0;i<n;i++) { scanf("%s",str); len[i]=strlen(str); for (int j=0;j<len[i];j++) s[i][str[j]-'a']++; } f[0]=inf; for (int i=1;i<bin[n];i++) { for (int j=0;j<n;j++) if (i&bin[j]) f[i]+=len[j]; memset(mn,inf,sizeof(mn)); for (int j=0;j<n;j++) if (i&bin[j]) for (int k=0;k<26;k++) mn[k]=min(mn[k],s[j][k]); int mx=0; for (int j=0;j<26;j++) mx+=mn[j]; for (int j=i;j;j=(j-1)&i) f[i]=min(f[i],f[j]+f[i-j]); if (mx<f[i]) f[i]-=mx; } PRintf("%d",f[bin[n]-1]+1); return 0;}
上一篇:利用摄像头拍照并保存

下一篇:poj1321

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表