【ZJOI2017 Round1练习】D4T2 trie(贪心,状压DP)
题意:现在 Matej 手上有 N 个英文小写字母组成的单词,
他想知道,如果将这 N 个单词中的字母分别进行重新排列,形成的字母树的节点数最少是多少。
n<=16,len[i]<=1000000
思路:
显然,如果我们希望 Trie 树的节点数尽量少,我们应该先将所有单词公共的字母拿出来,作为 Trie 树最上几层的初始链。
比如说我们有 aaab, baab 和 cab 三个单词,我们会将ab 挑出来,然后剩下的单词就变成了 aa, ab, c。
对于剩下的单词, 我们将其分成两个子集,( aa, ab)和( c),并分别再计算最长的公
共字母链。显然,当集合中有 n 个单词时,有 2^n种方式将这些单词分成两个子集。
由此,我们可以用状态压缩 dp 解决这个问题。一个状态由单词的子集来描述,也就是
说我们有 2^n个状态,并计算每一种子集形成 Trie 树需要的最少节点数,转移时枚举如何将
子集分裂成两个更小的子集,即可解决整个问题。整个算法总的时间复杂度为 O(3^n)。
1 var a:array[1..16,1..26]of longint; 2 dp,f:array[0..200000]of longint; 3 num:array[1..26]of longint; 4 n,i,j,k:longint; 5 ch:ansistring; 6 7 function min(x,y:longint):longint; 8 begin 9 if x<y then exit(x); 10 exit(y); 11 end; 12 13 procedure dfs(k,sta:longint); 14 var i,j:longint; 15 begin 16 if k=n+1 then 17 begin 18 f[sta]:=0; 19 for i:=1 to 26 do num[i]:=1<<25; 20 for i:=1 to n do 21 if sta and (1<<(i-1))>0 then 22 for j:=1 to 26 do num[j]:=min(num[j],a[i,j]); 23 for i:=1 to 26 do f[sta]:=f[sta]+num[i]; 24 exit; 25 end; 26 dfs(k+1,(sta<<1)+1); 27 dfs(k+1,sta<<1); 28 end; 29 30 begin 31 assign(input,'trie.in'); reset(input); 32 assign(output,'trie.out'); rewrite(output); 33 readln(n); 34 fillchar(dp,sizeof(dp),$7f); 35 for i:=1 to n do 36 begin 37 readln(ch); 38 k:=length(ch); dp[1<<(i-1)]:=k; 39 for j:=1 to k do inc(a[i,ord(ch[j])-ord('a')+1]); 40 end; 41 dfs(1,0); 42 for i:=1 to (1<<n)-1 do 43 begin 44 j:=i-1; 45 while j>0 do 46 begin 47 dp[i]:=min(dp[i],dp[j]+dp[i xor j]-f[i]); 48 j:=i and (j-1); 49 end; 50 end; 51 writeln(dp[(1<<n)-1]+1); 52 close(input); 53 close(output); 54 end.
null