Vještica

link

给定一些串,每个串可以进行重组,最小化这些串最后组成的Trie的结点数。

数据范围指向状压DP。很明显最后的答案和每个串一开始的字符顺序无关,于是可以记录每个串中每个字符的数量。然后发现在两个串合并的时候,为了使得树上结点最少,考虑贪心地把相同的字符排到前面去,于是最后的答案是 \(len_1+len_2-same\)。然后考虑推广,两个集合合并的时候由于两边都会把相同的部分作为每个串的前缀,所以这些串的共同部分是可以作为前缀的,更新即可。

会发现线性更新是错误的,比如下面的数据:

4
dabcrs
eabcrs
ex
ey

先合并后面三个串会使得 e 放在第一层,这样只能节省两个字符;而先合并前面两个串则可以节省后面的五个字符,所以需要枚举子集,用两个子集的答案去更新大的集合。复杂度为 \(O(3^NN)\)

code

#include<bits/stdc++.h>
//#define feyn
const int N=30;
const int M=1000010;
const int S=1<<16;
using namespace std;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
    wh*=f;return;
}
inline int min(int s1,int s2){
	return s1<s2?s1:s2;
}

char w[M];
int m,num[N][N];
int f[S],s[N];

signed main(){
	
	#ifdef feyn
	freopen("in.txt","r",stdin);
	#endif
	
	memset(f,0x3f,sizeof(f));
	read(m);
	for(int i=1;i<=m;i++){
		scanf("%s",w+1);
		int len=strlen(w+1);
		f[(1<<i-1)]=len;
		for(int j=1;j<=len;j++)num[i][w[j]-'a']++;
	}
	for(int i=1;i<(1<<m);i++){
		if(f[i]<M)continue;
		memset(s,0x3f,sizeof(s));
		for(int j=1;j<=m;j++){
			if((i&(1<<j-1))==0)continue;
			for(int k=0;k<26;k++)s[k]=min(s[k],num[j][k]);
		}
		for(int j=i;j;j=(j-1)&i)f[i]=min(f[i],f[j]+f[i-j]);
		for(int j=0;j<26;j++)f[i]-=s[j];
	}
	printf("%d\n",f[(1<<m)-1]+1);
	
	return 0;
}
posted @ 2022-07-26 17:52  Feyn618  阅读(16)  评论(0编辑  收藏  举报