1. 程式人生 > >NKOJ 4191 Trie (狀壓dp)

NKOJ 4191 Trie (狀壓dp)

P4191中山紀念中學 Trie

問題描述

字母(Trie)樹是一個表示一個字串集合中所有字串的字首的資料結構,其有如下特徵:

1.樹的每一條邊表示字母表中的一個字母
2.樹根表示一個空的字首
3.樹上所有其他的節點都表示一個非空字首,每一個節點表示的字首為樹根到該節點的路徑上所有字母依次連線而成的字串。
4.一個節點的所有出邊(節點到兒子節點的邊)中不存在重複的字母。
    
現在Matej手上有N個英文小寫字母組成的單詞,他想知道,如果將這N個單詞中的字母分別進行重新排列,形成的字母樹的節點數最少是多少。

輸入格式

第一行包含一個正整數N(1<=N<=16)
接下來N行每行一個單詞,每個單詞都由小寫字母組成。
單詞的總長度不超過1,000,000。

輸出格式

輸出僅一個正整數表示N個單詞經過重新排列後,字母樹的最少節點數。

樣例輸入

10
jgda
dbfdjj
hehegdfh
faeejic
acagdgfcjc
jifiigdbif
fdbdii
ch
c
adccdd

樣例輸出

42

此題題目是trie,但是和trie沒有什麼太大關係,只需要知道,對於這些串,將他們的公共部分作為公共字首是最優的即可。
那麼首先考慮兩個串,答案顯然是他們的長度和減去公共部分。
那麼再考慮三個串,顯然可能出現兩兩的公共部分大於三個的公共部分的情況,這種時候trie樹必然會出現分叉,我們需要考慮如何分叉,分成二叉或三叉,再者哪些串在同一子樹上,既然一定會分成多顆子樹,那麼我們可以直接將這三個串拆成兩個子集,先求出兩個子集的最優解,然後減去公共部分即可。
也就是說,令F

[S]表示將S集合中的字串弄到一棵樹上的最少節點數,那麼有
F[S]=min{F[k]+F[Sxork]}S

程式碼:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#define N 66666
#define M 1000005
using namespace std;
int n,S,F[N],cnt[20][200],A[200],sum;
char s[M];
int main()
{
    int i,j,k;
    scanf
("%d",&n); S=(1<<n)-1; for(i=1;i<=n;i++) { scanf("%s",&s); k=strlen(s); for(j=0;j<k;j++)cnt[i][s[j]]++; } for(i=1;i<=S;i++) { memset(A,60,sizeof(A));sum=0; for(j=1;j<=n;j++) if(i&(1<<j-1)) { for(k='a';k<='z';k++) { F[i]+=cnt[j][k]; A[k]=min(A[k],cnt[j][k]); } } for(j='a';j<='z';j++)sum+=A[j]; for(j=i&i-1;j;j=i&j-1)F[i]=min(F[i],F[i^j]+F[j]); if(F[i]>sum)F[i]-=sum; } printf("%d",F[S]+1); }