1. 程式人生 > 其它 >題解【P6289 [COCI2016-2017#1] Vještica】

題解【P6289 [COCI2016-2017#1] Vještica】

傳送門

$\texttt{Description}$

給定 $n$ 個字串,你可以將任意個字串重組,使得儲存這些單詞的 $\text{Trie}$ 數的節點數儘量小。$1\le n\le 16$。

$\texttt{Solution}$

第一眼發現 $n\le 16$,可以考慮狀壓 $\texttt{DP}$。

我們設 $f[s]$ 表示插入完集合 $s$ 中的所有字串的最小節點數。

那麼顯然答案是 $f[S]$,$S$ 表示所有字串的集合。

考慮轉移。

先解決一種簡單的情況,假設說只有兩個字串 $A,B$,那麼將它們插入到 $\text{Trie}$ 樹上後所需的節點數為 $len_A+len_B-\text{same}(A,B)$,$\text{same}(A,B)$ 指 $A$ 和 $B$ 可能的最長公共字首。

這種簡單的情況是可以推廣到 $n$ 個字串的。

對於 $f[s]$,我們考慮列舉 $s$ 的子集 $s'$,那麼 $s\operatorname{xor} s'$ 是 $s$ 的另一個子集 $s''$。

我們直接對於 $s'$ 和 $s''$ 看做兩個字串,長度分別為 $f[s']$ 和 $f[s'']$,所以就可以歸結為兩個字串的情況。

於是得到了最終的狀態轉移方程:$f[s]=\min_{s'\in s}\{f[s']+f[s\operatorname{xor} s']-\operatorname{same}(s)\}$。

考慮函式 $\operatorname{same}$ 如何求。

我們貪心的想,對於一個字元 $c$,我們考慮集合中所有字串中 $c$ 的數量,找到最小的那一個,記為 $x$,是不是說明公共的長度又能減少 $x$,因為所有的字串都有這 $x$ 個字元 $c$。我們對於每一個字元都求一遍相應的 $x$,求和,便是 $\operatorname{same}(s)$ 的值。

細節留給讀者自行處理。

貼一下程式碼:

#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN(18);
const int MAXM(1e6+10);
const int INF(1e9+10);
int n;
char s[MAXN][MAXM]; 
int fac[MAXN],len[MAXN];
inline int lowbit(int x){return x&(-x);}
inline int Min(int x,int y){return x<y?x:y;}
int pos[MAXN];
int dp[1<<MAXN];
int mp[MAXN][27];
int main()
{
	scanf("%d",&n);
	for(register int i=1;i<=n;i++) scanf("%s",s[i]+1);
	fac[0]=1;
	for(register int i=1;i<=n;i++) fac[i]=fac[i-1]<<1;
	for(register int i=1;i<=n;i++)
	{
		len[i]=strlen(s[i]+1);
		for(register int j=1;j<=len[i];j++)
		{
			int c=s[i][j]-'a'+1;
			mp[i][c]++;
		}
	}
	for(register int i=1;i<fac[n];i++)
	{
		if(i==lowbit(i))//單獨選了一個
		{
			for(register int j=1;j<=n;j++)
				if(i&fac[j-1])
				{
					dp[i]=len[j];
					break;
				}
			continue;
		}
		int sum=0,tot=0,minlen=INF;
		for(register int j=1;j<=n;j++)
			if(i&fac[j-1]) pos[++tot]=j,minlen=Min(minlen,len[j]);
			//找出i中有哪些字串,將編號記錄到pos中 
		for(register int c=1;c<=26;c++)//列舉每一個c,找相應的x 
		{
			int minn=INF;
			for(register int j=1;j<=tot;j++) minn=Min(minn,mp[pos[j]][c]);
			sum+=minn;//求 same 的值 
		}
		dp[i]=INF;
		for(register int j=(i-1)&i;j;j=(j-1)&i)//列舉i的子集 
			dp[i]=Min(dp[i],dp[j]+dp[i^j]-sum);
	}
	printf("%d\n",dp[fac[n]-1]+1);
	return 0;
}

$$\texttt{The End.by UF}$$