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

来源:互联网 发布:画江湖之换世门生知乎 编辑:程序博客网 时间:2024/05/16 07:52

题意

给出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;}
0 0
原创粉丝点击