1. 程式人生 > >【bzoj2121】字符串遊戲 區間dp

【bzoj2121】字符串遊戲 區間dp

IT 進行 zoj 復雜度 highlight AC sof print bzoj

題目描述

給你一個字符串L和一個字符串集合S,如果S的某個子串在S集合中,那麽可以將其刪去,剩余的部分拼到一起成為新的L串。問:最後剩下的串長度的最小值。

輸入

輸入的第一行包含一個字符串,表示L。 第二行包含一個數字n,表示集合S中元素個數。 以下n行,每行一個字符串,表示S中的一個元素。 輸入字符串都只包含小寫字母。

輸出

輸出一個整數,表示L的最短長度。

樣例輸入

aaabccd
3
ac
abc
aaa

樣例輸出

2


題解

我們考慮:每次刪除連續的一段,對應到原串上即:刪除 $[l,r]$ 中所有未被刪除的字符。其中 $l,r$ 都未被刪除。

這樣就相當於選擇若幹區間來刪除。

註意到選擇的任意兩個區間要麽包含要麽不相交(相離),對於相鄰的相離的也可以看作是包含(右區間左端點看作是左區間左端點,即一個空位置),因此只有包含關系。

那麽如下圖:

技術分享圖片

先選擇 $[b,c]$ 的串 $S$ ,再選擇 $[a,d]$ 的串 $T$ ,可以看作是處理出 $[a,b)$ 能夠匹配到 $T$ 的中間位置,$[b,c]$ 能夠匹配到 $S$ 的結束位置(即刪除掉),進而推知 $[a,c]$ 能夠匹配到 $T$ 的中間位置,再向右匹配得知 $[a,d]$ 能夠匹配到 $T$ 的結束位置。

考慮區間dp。設 $f[l][r]$ 表示 $[l,r]$ 是否可以全部刪掉,再設 $g[l][r][i][j]$ 表示 $[l,r]$ 是否能夠刪成第 $i$ 個字符串的前 $j$ 個字符。

那麽考慮區間 $[l,r]$ ,如果進行匹配的話轉移為 $g[l][r][i][j]=g[l][r-1][i][j-1]$ ,前提條件 $str[r]==w[i][j]$ ,即區間右端點和第 $i$ 個串的第 $j$ 個字符相同。

如果不進行匹配的話,$r$ 一定在某個 $[k,r]$ 中被消掉,因此枚舉 $k\in[l,r]$ ,轉移為 $g[l][r][i][j]=g[l][k-1][i][j]\&\&f[k][r]$ 。

根據 $f$ 的定義有轉移 $f[l][r]=g[l][r][i][len[i]]$ 。

這樣我們就能夠推出 $f$ 和 $g$ 。

再考慮答案:設 $h[i]$ 表示前 $i$ 個字符的答案,那麽 $h[i]=h[i-1]+1$ ;如果某個 $j$ 滿足 $f[j][i]=1$ ,即 $[j,i]$ 能刪掉,則還有 $h[i]=h[j-1]$ 。

最終答案就是 $h[n]$ 。

時間復雜度 $O(n^3·m·len)$

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
bool f[155][155] , g[155][155][35][25];
char str[155] , w[35][25];
int c[35] , ans[155];
int main()
{
	int n , m , len , l , r , i , j , k;
	scanf("%s%d" , str + 1 , &m) , n = strlen(str + 1);
	for(i = 1 ; i <= m ; i ++ ) scanf("%s" , w[i] + 1) , c[i] = strlen(w[i] + 1);
	for(i = 1 ; i <= n ; i ++ )
	{
		f[i][i - 1] = 1;
		for(j = 1 ; j <= m ; j ++ )
			g[i][i - 1][j][0] = 1;
	}
	for(len = 1 ; len <= n ; len ++ )
	{
		for(l = 1 ; l <= n - len + 1 ; l ++ )
		{
			r = l + len - 1;
			for(i = 1 ; i <= m ; i ++ )
			{
				for(j = 1 ; j <= c[i] ; j ++ )
					if(str[r] == w[i][j])
						g[l][r][i][j] |= g[l][r - 1][i][j - 1];
				for(j = 0 ; j <= c[i] ; j ++ )
					for(k = l ; k <= r ; k ++ )
						g[l][r][i][j] |= g[l][k - 1][i][j] & f[k][r];
			}
			for(i = 1 ; i <= m ; i ++ ) f[l][r] |= g[l][r][i][c[i]];
		}
	}
	for(i = 1 ; i <= n ; i ++ )
	{
		ans[i] = ans[i - 1] + 1;
		for(j = 1 ; j <= i ; j ++ )
			if(f[j][i])
				ans[i] = min(ans[i] , ans[j - 1]);
	}
	printf("%d\n" , ans[n]);
	return 0;
}

【bzoj2121】字符串遊戲 區間dp