明确是从里面往外扩展的就比较好想
// 回文串的性质:如果两端字符相同,可以同时去掉;如果不同,去掉一个字符
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e3 + 10;
map<char, int> mp; // 存储每个字符的最小代价
int n, m, dp[N][N]; //dp[i][j]表示将区间[i,j]变为回文串需要花费的最小代价
char s[N];
int main() {
// 输入字符种类数和字符串长度
cin >> n >> m;
cin >> (s + 1); // 输入字符串,从下标1开始存储,方便处理
// 输入每个字符的两种代价,并取最小值存储
for (int i = 1; i <= n; i++) {
char c[2]; // 存储字符
int x, y; // 存储两种代价
cin >> (c + 1) >> x >> y;
mp[c[1]] = min(x, y); // 保存每个字符的最小代价
// 初始化字符(键)对应的值
}
// 初始化dp数组,初始值设为一个很大的数(表示无穷大)
for (int i = 1; i <= m; i++)
for (int j = 1; j <= m; j++) dp[i][j] = 1e9;
// 初始化边界条件
for (int i = 1; i <= m; i++) {
dp[i][i] = 0; // 长度为1的子串(单个字符)不需要代价
dp[i + 1][i] = 0; // 空串的代价为0
//aa会两个都删去表现为空串
}
/*
* 动态规划求解:
* dp[i][j] 表示将子串 s[i...j] 转换为回文串的最小代价
* 状态转移分两种情况:
* 1. 如果 s[i] == s[j],则可以同时去掉这两个字符,代价为 dp[i+1][j-1]
* 2. 如果 s[i] != s[j],则需要去掉其中一个字符,代价为 dp[i+1][j] + mp[s[i]] 或 dp[i][j-1] + mp[s[j]]
*/
for (int l = 2; l <= m; l++) { // 枚举子串长度
for (int i = 1; (i + l - 1) <= m; i++) { // 枚举子串起点
int j = i + l - 1; // 子串终点
if (s[i] == s[j]) {
// 如果两端字符相同,可以同时“去掉”
// 这里的去掉指的是不用花费代价就可以变成回文串
dp[i][j] = min(dp[i][j], dp[i + 1][j - 1]);
} else {
// 如果两端字符不同,至少需要去掉一个字符
dp[i][j] = min(dp[i][j], dp[i + 1][j] + mp[s[i]]); // 去掉左端字符
dp[i][j] = min(dp[i][j], dp[i][j - 1] + mp[s[j]]); // 去掉右端字符
}
}
}
// 输出最终结果:将整个字符串 s[1...m] 转换为回文串的最小代价
cout << dp[1][m] << endl;
return 0;
}