• 题解 Vjestica


    题目简述

    有n个字符合集,求其前缀树的最少结点个数。

    Solve

    首先(N)最大才是(16),自然想到的就是压位(D)P,定义(F[s])表示状态(s)下最短的(Trie)大小(-1)(除去根节点)

    (F[s]=?),转移方程并不好推,但是我们有一个贪心的想法

    因为单词可以任意排序,所以第一步就是将字符串转化为哈希表

    我们将问题简化,如果两个字符串当作(Trie)合并,最小大小是多少?

    自然可以想到(Union(String1,String2)=Length(String1)+Length(String2)-String1)(String2)的最大可重部分

    于是针对两个(Trie)的合并,我们也可以采取同样的方法

    (Union(Trie1,Trie2)=Size(Trie1)+Size(Trie2)-Trie1)(Trie2)的最大可重部分

    由于一个(Trie)由多个(String)合并而来,于是我们可以用(L[s])表示合并状态为s的最大可重部分

    自然的(L[s])很好求,我们将每种字符在每一个s状态包含字符串中出现次数最小值的加和即是(L[s])

    (cnt[s][j])表示状态s的串中字符j出现的次数

    我们可以用如下代码段实现:

    for (int i=0;i<N;i++){
    	scanf("%s",S);
    	for (int j=0;S[j];j++) cnt[1<<i][S[j]-'a']++;
    	//初始化,仅有1个字符串
    }
    for (int i=0;i<26;i++) cnt[0][i]=INF;
    //初始化
    for (int s=1;s<1<<N;s++){
    	int x=s&-s;
    	for (int i=0;i<26;i++) cnt[s][i]=min(cnt[s-x][i],cnt[x][i]);
    	//s&-s表示s最末尾的那一个的元素的单独的状态值,我们这么推算便能从上一状态推算而来,快了许多
    	for (int i=0;i<26;i++) L[s]+=cnt[s][i];
    }
    这样的话接下来的转移方程就容易得多了
    for (int s=1;s<1<<N;s++){
    	F[s]=L[s];
    	for (int i=0;i<N;i++) if (s&(1<<i)) F[s]+=L[1<<i]-L[s];
    	//先把所有包含的String合并,这种情况一定可行
    	for (int x=(s-1)&s;x;x=(x-1)&s)	F[s]=min(F[s],F[x]+F[s-x]-L[s]);
    	//然后试着将包含的两个Trie合并,提示:(s-1)&s=s-(s&-s),也就是每次多抠掉一个元素之后留下的状态值
    }
    

    Code

    #include<bits/stdc++.h>
    using namespace std;
    int N,cnt[1<<16][26],F[1<<16],L[1<<16];
    char S[1000005];
    int main(){
    	freopen("vjestica.in","r",stdin);
    	freopen("vjestica.out","w",stdout);
    	scanf("%d",&N);
    	for (int i=0;i<N;i++){
    		scanf("%s",S);
    		int len=strlen(S);
    		for (int j=0;j<len;j++) cnt[1<<i][S[j]-'a']++;
    	}
    	for (int i=0;i<26;i++) cnt[0][i]=1<<30;
    	for (int s=1;s<1<<N;s++){
    		int x=s&-s;
    		for (int i=0;i<26;i++) cnt[s][i]=min(cnt[s-x][i],cnt[x][i]);
    		for (int i=0;i<26;i++) L[s]+=cnt[s][i];
    	}
    	for (int s=1;s<1<<N;s++){
    		F[s]=L[s];//每个串都要切掉最长的公共的部分,所以初始的时候应该单独+1个 
    		for (int i=0;i<N;i++) if (s&1<<i) F[s]+=L[1<<i]-L[s];//先切掉每个串的公共部分,获得初始 
    		for (int x=s-1&s;x;x=x-1&s)	F[s]=min(F[s],F[x]+F[s-x]-L[s]);//转移方程也很好理解的,枚举每个串单独拎出来而已 
    	}
    	printf("%d
    ",F[(1<<N)-1]+1);
    	return 0;
    }
    
  • 相关阅读:
    .net framework v4.5.2
    sql数据库不允许保存更改和保存失败解决方法
    如何查看笔记本电脑型号
    Premiere 5.0/5 .5菜单详解
    Python编辑器英文菜单的中文翻译及解释
    SqlServer--常用数据查询
    pycharm上方菜单栏不见了如何恢复
    笔记本电脑的f1到f12怎么按
    C#的访问权限
    封装,继承和多态知识点汇总
  • 原文地址:https://www.cnblogs.com/martian148/p/13887218.html
Copyright © 2020-2023  润新知