@UPC 6902 @中山纪念中学20170304 Trie树(状压DP)

题目描述

字母(Trie)树是一个表示一个字符串集合中所有字符串的前缀的数据结构,其有如下特征:
1.树的每一条边表示字母表中的一个字母
2.树根表示一个空的前缀
3.树上所有其他的节点都表示一个非空前缀,每一个节点表示的前缀为树
根到该节点的路径上所有字母依次连接而成的字符串。
4.一个节点的所有出边(节点到儿子节点的边)中不存在重复的字母。

 

单词“A”“to”“tea”“ted”“ten”“i”“in”“inn”对应的Trie树

现在Matej手上有N个英文小写字母组成的单词,他想知道,如果将这N个单词中的字母分别进行重新排列,形成的字母树的节点数最少是多少。

 

输入

第一行包含一个正整数N(1<=N<=16)
接下来N行每行一个单词,每个单词都由小写字母组成。
单词的总长度不超过1,000,000。

 

 

输出

输出仅一个正整数表示N个单词经过重新排列后,字母树的最少节点数。

 

样例输入

3
a
ab
abc

 

样例输出

4

[思路]

状态压缩.

对于两个串, aaabb 与 aabccc   打乱内部顺序, 只需考虑字母相同个数即可,  最少节点数 = 两串长度和-公共串

=  5+6-3=8

然而当串的个数大于2个时,这个结论是不成立的.发现N 只有16,  可以采用状态压缩的思路,把串两两分组,分组的个数即为2^n 个. 这样分下去是可行的, 用0 代表不选,1 代表选择, 那么 最终的答案就是 1111111 ..1  n个1 中 然后加上空节点一个.

然后 学习到了  骚操作--: for(int j=i&i-1;j;j= i&j-1) 遍历所有的子集 

[代码]

#include <iostream>
#include <bits/stdc++.h>

#define rep(i,a,n) for(int i=a;i<n;i++)
#define per(i,a,n) for(int i=n-1;i>=a;i--)

typedef long long ll;

using namespace std;

const int mod= 1e9+7;
const int maxn = 1e6+10;
char str[maxn];
int dp[1<<17];
int c[30];
int cont[50][50];
int len[50];
int n;

int main()
{

    scanf("%d",&n);
    rep(i,0,n)
    {
        scanf("%s",str);
        len[i] = strlen(str);
        rep(j,0,len[i]) // count
            cont[i][str[j]-'a']++;
    }
    rep(i,0,1<<n)
    {
        dp[i] = 0;
        memset(c,0x3f,sizeof(c));
        rep(j,0,n)
        {
            if( (1<<j)&i )// i 集合内 的所有单词
            {

                dp[i]+=len[j];
                rep(k,0,26)
                {
                     //dp[i] += cont[j][k]; // len
                     c[k] = min(c[k],cont[j][k]);  // 统计 最小的字母个数
                }


            }
        }
        int sum = 0;
        rep(k,0,26) sum+=c[k];// 最小公共前缀
        for(int j=i&i-1;j;j= i&j-1)//骚操作之 遍历 所有的子集
        {
           // cout<<dp[i]<<" "<<(dp[j^i]+dp[j])<<" "<<sum<<endl;
            dp[i] = min(dp[i],dp[j^i]+dp[j]-sum); // 两个字串的长度 - 公共字串
            //cout<<i<<" "<<j<<" "<<(j^i)<<endl;
        }

    }
    printf("%d\n",dp[(1<<n)-1]+1);// + 空节点
    return 0;
}
/*
5
aaabbab
abcd
cdeag
abgf
aabb
*/

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值