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)\)
我們需要更巧妙的狀態設計。
強行欽定區間的最後一位(\(j\))是還沒有被刪掉的,或者說是和非當前區間裡的數一起刪掉的。可以理解為是上一種狀態裡的 "\(k\)" 個位置中的一員。
定義新狀態 \(\text{dp}_2(i,j,x)\) 表示只考慮 \([i,j]\) 這段區間,\(j\) 後面有 \(x\) 個數是(要在之後的過程裡)和它一起被刪掉的。當然,既然只考慮了區間 \([i,j]\),那我們也不知道 \(j\) 後面的串長什麼樣。所以這 \(x\)
轉移有兩種:
- 把一段數刪掉。這段是就是指 \(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;
}