POJ 1141 Brackets Sequence【题解报告|有趣的区间dp】

今天很高兴,十几个清华本科和30个左右985专业第一,经过三天的角逐,终于让我拿到了清深的offer,夏令营的第一个收获!可以安心的九推冲击清软了

题目大意

给一些括号,求让它们合法最少要加上多少括号,并输出加上后的结果,合法的定义:

1.空串是合法的
2.若S是合法的,则[S]和(S)均是合法的
3.若A和B是合法的,则AB是合法的

思路分析

一道类似的区间dp问题,区间dp问题写的不多,右边这道回文子串算是一个,但是自己思维却是被这道题固化了,遇到最小括号匹配时也想用相同的dp方法来作,先附上我第一遍的代码:

#define inf 0x3f3f3f3f
#define ll long long
#define vec vector
#define P pair
#define MAX 105

string ds[MAX][MAX], s;
int dp[MAX][MAX];
map<char, char> ma;

int main() {
	ma['['] = ']', ma[']'] = '[', ma['('] = ')', ma[')'] = '(';
	while (cin >> s) {
		memset(dp, 0, sizeof(dp));
		int len = s.size();
		for (int i = 0; i < len; i++) {
			dp[i][i] = 1;
			for (int j = 0; j < len; j++) {
				ds[i][j] = "";
			}
			if (s[i] == '(' || s[i] == '[')ds[i][i] += s[i], ds[i][i] += ma[s[i]];
			else ds[i][i] += ma[s[i]], ds[i][i] += s[i];
		}

		for (int k = 2; k <= len; k++) {//枚举长度
			for (int beg = 0; beg + k - 1 < len; beg++) {//枚举起点
				int i = beg, j = beg + k - 1;
				//是左侧的符号,而且可以匹配
				if ((s[i] == '(' || s[i] == '[') && ma[s[i]] == s[j])
					dp[i][j] = dp[i + 1][j - 1], ds[i][j] = s[i] + ds[i + 1][j - 1] + s[j];
				else {
					if (dp[i + 1][j] <= dp[i][j - 1]) {
						dp[i][j] = dp[i + 1][j] + 1;
						if (s[i] == '(' || s[i] == '[')
							ds[i][j] += s[i], ds[i][j] += ma[s[i]] + ds[i + 1][j];
						else
							ds[i][j] + ma[s[i]], ds[i][j] += s[i] + ds[i + 1][j];
					}
					else {
						dp[i][j] = dp[i][j - 1] + 1;
						ds[i][j] += ds[i][j - 1];
						if (s[i] == '(' || s[i] == ']')
							ds[i][j] += s[i], ds[i][j] += ma[s[i]];
						else
							ds[i][j] += ma[s[i]], ds[i][j] += s[i];
					}
				}
			}
		}
		cout << ds[0][len - 1] << endl;
	}
}

这个代码使用了判断最少添加元素组成回文串的一种简单思路,

  • 枚举长度,枚举起点(这是区间dp的一般做法,是不错的
  • 但是dp过程中有一个重大错误,对回文子串来说,确实是只需要判断两端是否相等,然后相等的话直接dp[i][j]=dp[i-1][j+1],否则要么在左侧加个元素和区间右端匹配,要么在右侧加个元素和区间左侧匹配,

于是我就写出了上面的代码,但是需要注意的是,这里可能有()[()]这种形式,用上面的代码写出来结果却是不对的,因为左右端点( ]不相等,因此会找 d p [ i + 1 ] [ j ] dp[i+1][j] dp[i+1][j] d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1]中最小的。

显然这道题我们不能单纯的拿端点说事,那么我们如何找到最少添加的符号数?值得注意的是,我们如果在左右端点之间中任意找一点,他的左侧组成合法串需要添加的符号数+右侧组成合法串需要添加的符号数最小的话,我们就找到了结果,所以递推关系式是这样的:

i ≤ k < j , d p [ i ] [ j ] = m i n ( d p [ i ] [ k ] , d p [ k + 1 ] [ j ] ) i \leq k < j,dp[i][j] = min(dp[i][k],dp[k + 1][j]) ik<jdp[i][j]=min(dp[i][k]dp[k+1][j])

不要被自己的经验误导!多思考

//1040K	282MS	
#define inf 0x3f3f3f3f
#define ll long long
#define vec vector
#define P pair
#define MAX 105

string ds[MAX][MAX], s;
int dp[MAX][MAX];
map<char, char> ma;

int main() {
	ma['['] = ']', ma[']'] = '[', ma['('] = ')', ma[')'] = '(';
	getline(cin, s);
	if (s == "") { cout << endl; return 0; }
	memset(dp, 0, sizeof(dp));
	int len = s.size();
	for (int i = 0; i < len; i++) {
		dp[i][i] = 1;
		for (int j = 0; j < len; j++) {
			ds[i][j] = "";
		}
		if (s[i] == '(' || s[i] == '[')ds[i][i] += s[i], ds[i][i] += ma[s[i]];
		else ds[i][i] += ma[s[i]], ds[i][i] += s[i];
	}

	for (int k = 2; k <= len; k++) {//枚举长度
		for (int beg = 0; beg + k - 1 < len; beg++) {//枚举起点
			int i = beg, j = beg + k - 1;
			dp[i][j] = inf;
			//先考虑两边为端点可不可以
			if ((s[i] == '(' || s[i] == '[') && ma[s[i]] == s[j])
				dp[i][j] = dp[i + 1][j - 1], ds[i][j] = s[i], ds[i][j] += ds[i + 1][j - 1] + s[j];

			for (int t = i; t < j; t++) {
				if (dp[i][t] + dp[t + 1][j] < dp[i][j]) {
					dp[i][j] = dp[i][t] + dp[t + 1][j];
					ds[i][j] = ds[i][t] + ds[t + 1][j];
				}
			}
		}
	}
	cout << ds[0][len - 1] << endl;
}

你可能感兴趣的:(DP&状态压缩DP)