CF1107E Vasya and Binary String

CF1107E Vasya and Binary String

Codeforces, Educational Codeforces Round 59 (Rated for Div. 2), CF1107E Vasya and Binary String

题目大意

题目链接

给定一个长度为 \(n\) 的 01 串 \(s\),和一个长度为 \(n\) 的正整数序列 \(a\)

你需要进行若干次操作,直到字符串 \(s\) 为空。每步操作中,可以选择当前串 \(s\) 里的一段相同字符组成的连续子串,并将其删掉。然后它两边的字符会自动拼接起来。设当前删除的子串长为 \(x\),那么可以获得 \(a_x\) 的收益。

求所有操作完成后,能获得的收益之和的最大值。

数据范围:\(1\leq n\leq 100\)\(1\leq a_i\leq 10^9\)

本题题解

考虑区间 DP。容易想到的一个状态设计是:\(\text{dp}(i,j,k,0/1)\),表示只考虑 \([i,j]\) 这段区间,还剩下 \(k\)\(0\)\(1\) 没有删除时,能得到的最大收益(不包括这 \(k\)\(0/1\) 的收益)。转移需要枚举 \(i,j\) 中间的断点,再枚举左边剩多少个 \(0/1\) 没有删掉(左边的知道了,用 \(k\) 减左边的就是右边的)。特别地,\(k = 0\) 时可以把两边的、剩余的部分一起消掉,并产生一个收益。时间复杂度 \(\mathcal{O}(n^5)\),无法通过本题。另外,如果把状态简单定义为 \(\text{dp}(i,j)\),也能得到一个 \(\mathcal{O}(n^5)\) 做法,这里不细说了。

我们需要更巧妙的状态设计。

强行钦定区间的最后一位(\(j\))是还没有被删掉的,或者说是和非当前区间里的数一起删掉的。可以理解为是上一种状态里的 "\(k\)" 个位置中的一员。

定义新状态 \(\text{dp}_2(i,j,x)\) 表示只考虑 \([i,j]\) 这段区间,\(j\) 后面有 \(x\) 个数是(要在之后的过程里)和它一起被删掉的。当然,既然只考虑了区间 \([i,j]\),那我们也不知道 \(j\) 后面的串长什么样。所以这 \(x\) 个数是我们想象出来的,可能根本不存在这些数,但这不重要,我们只要保证能从符合实际的状态转移到答案即可。答案就是 \(\text{dp}_2(1,n,0)\)

转移有两种:

  • 把一段数删掉。这段是就是指 \(j\) 和它后面的数。于是有转移:\(\text{dp}_2(i,j,x)\leftarrow \text{dp}_2(i,j,0) + a_{x + 1}\)
  • 合并两段区间。枚举断点 \(p\in[i,j)\),则:\(\text{dp}_2(i,j,x)\leftarrow \text{dp}_2(i,p,x + 1) + \text{dp}_2(p + 1, j - 1, 0)\)。这相当于把 \(p\)\(j\)、以及 \(j\) 后面的 \(x\) 个数,一起删掉,所以执行这种转移的前提是 \(s_{p} = s_{j}\)。并且,这次删除的收益已经算在 \(\text{dp}_2(i,j,x)\) 里了,所以这里不需要加上。

感性理解:在第一种转移里,我们算上想象中的 \(x\) 的收益,相当于是开出了一张空头支票。而第二种转移,发现 \(x\) 减小了 \(1\),这代表这我们的承诺在一步步兑现。具体来说,就是最后的那一位 \(j\),从空头支票里走到了现实中。

于是,这个复杂的操作过程,就被转化为了简单的“开支票,再不断兑现”的过程。并且同一时间只需要处理一张支票,这是因为任意两个操作区间,要么相互并列,要么完全包含,不可能出现相交。\([p + 1, j - 1]\) 其实就是这种“完全包含”的情况,它是一个完全自闭的区间(自我封闭,也就是已经完结了)。特别地,这个区间的长度可能为 \(0\),即 \(p + 1 = j\),此时我们认为 \(\text{dp}_2(p + 1, j - 1, 0) = 0\)

转移的复杂度主要来自于枚举 \(p\),是 \(\mathcal{O}(n)\) 的。总时间复杂度 \(\mathcal{O}(n^4)\)

参考代码

// problem: CF1107E
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 100;
const ll LL_INF = 1e15;

int n, a[MAXN + 5];
char s[MAXN + 5];

ll dp[MAXN + 5][MAXN + 5][MAXN + 5];

int main() {
	cin >> n;
	cin >> (s + 1);
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	
	for (int i = 1; i <= n; ++i) {
		for (int j = i; j <= n; ++j) {
			for (int k = 0; k <= n; ++k) {
				dp[i][j][k] = -LL_INF;
			}
		}
		for (int k = 0; k <= n - i; ++k) {
			dp[i][i][k] = a[k + 1];
		}
	}
	
	for (int len = 2; len <= n; ++len) {
		for (int i = 1; i + len - 1 <= n; ++i) {
			int j = i + len - 1;
			for (int k = 0; k <= n - j; ++k) {
				ckmax(dp[i][j][k], dp[i][j - 1][0] + a[k + 1]);
			}
			
			for (int k = 0; k <= n - j; ++k) {
				for (int l = i; l <= j - 1; ++l) if (s[l] == s[j]) {
					ckmax(dp[i][j][k], dp[i][l][k + 1] + dp[l + 1][j - 1][0]);
				}
			}
		}
	}
	cout << dp[1][n][0] << endl;
	
	return 0;
}
posted @ 2020-12-26 22:16  duyiblue  阅读(172)  评论(1编辑  收藏  举报