1. 程式人生 > 實用技巧 >CF1107E Vasya and Binary String

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}(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)\) 裡了,所以這裡不需要加上。特別地,右半段區間可以長度為 \(1\),此時 \(p + 1 = j\)\(\text{dp}_2(p + 1, j - 1, 0)\) 的值就等於 \(0\)

感謝理解:在第一種轉移裡,我們算上想象中的 \(x\) 的收益,相當於是開出了一張空頭支票。而第二種轉移,發現 \(x\) 減小了 \(1\),這代表這我們的承諾在一步步兌現。具體來說,就是最後的那一位 \(j\),從空頭支票裡走到了現實中。

轉移的複雜度主要來自於列舉 \(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;
}