用solve(l, r, prefix)代表区间l开始r结束、带了prefix个前缀str[l](即l前面的串化简完压缩成prefix-1个str[l],加上str[l]共有prefix个)的最大值。
每层可以选择:
1.直接“提现”,把起始位和前面的“存款”直接提出来,再计算l+1~r区间的值;
2.继续“存款”,枚举和str[l]相同的位置,把中间先合并了然后删了,把prefix+1,即把枚举的这位和之前的存款粘在一起,然后计算i~r的值,更新ans。
乍一想会觉得每次只把两个粘在一起这行吗,实际上一个区间里可能会好几个粘在一起啊?但其实:假设i < j,算i的时候i~r的那部分的存款就为prefix+1了,即我们选取j的时候,i已经在里了,所以只是子区间看起来选两个而已,实际上从大的范围来看已经选了若干个。
然后这个方法居然不用提前预处理——真·a[i],可能是搞的过程中就背包了吧?只是预处理的会跑得快一些。
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 using namespace std; 6 7 typedef long long ll; 8 int n; 9 ll a[105], dp[105][105][105]; 10 char s[105]; 11 12 ll solve(int l, int r, int prefix) { 13 if (l > r) 14 return 0; 15 if (l == r) 16 return a[prefix]; 17 18 ll &ret = dp[l][r][prefix]; 19 if (ret) 20 return ret; 21 22 ret = a[prefix] + solve(l + 1, r, 1); 23 for (int i = l + 1; i <= r; i++) { 24 if (s[i] == s[l]) 25 ret = max(ret, solve(l + 1, i - 1, 1) + solve(i, r, prefix + 1)); 26 } 27 28 return ret; 29 } 30 31 int main() { 32 cin >> n >> (s + 1); 33 34 for (int i = 1; i <= n; i++) 35 cin >> a[i]; 36 37 for (int i = 2; i <= n; i++) { 38 for (int j = i; j > 0; j--) { 39 a[i] = max(a[i], a[j] + a[i - j]); 40 } 41 } 42 43 cout << solve(1, n, 1) << endl; 44 return 0; 45 }