区间DP
区间DP
区间DP常用模板
所谓区间DP,指在一段区间上进行动态规划,一般做法是由长度较小的区间往长度较大的区间进行递推,最终得到整个区间的答案,边界就是长度为1和2的区间
所有的区间DP问题,有两种写法
1、第一维都是枚举区间长度,第二维枚举起点,先求出小区间的属性值,进而扩展到大区间
核心思想:小区间 ---> 大区间
for (int i = 1; i <= n; i++) {
dp[i][i] = 初始值
}
for (int len = 2; len <= n; len++) //区间长度
for (int i = 1; i + len - 1 <= n; i++) { //枚举起点
int j = i + len - 1; //区间终点
for (int k = i; k < j; k++) { //枚举分割点,构造状态转移方程
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + w[i][j]);
}
}
2、第一维从最尾部开始枚举起点,第二维枚举终点(从短到长)
for(int i = n;i >= 1;i--) { // 起点从尾部开始
dp[i][i] = 初始值;
for(int j = i + 1;j <= n;j++) { // 终点
f[i][j] = max(.......)
}
}
1、区间DP复习
2、石子合并
题目描述:给定一个字符串,通过增加或删除相关字母形成回文串,每一个不同的字母增加和删除操作需要的花费不同,求形成回文串所需的最小代价
思路
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 2010;
char s[N];
int n, m;
int cost[30];
int dp[N][N];//dp[i][j] 存储从 i 到 j 这一段转换成回文子序列需要的最小花费
int main() {
ios::sync_with_stdio(false);//将stdio解除绑定,使得cin与scanf效率相差无几
int a, b;
char t;
while (cin >> n >> m) {
cin >> s + 1;
while (n--) {
cin >> t >> a >> b;
cost[t - 'a'] = min(a, b);
}
// for (int i = m; i >= 1; i--) {
// dp[i][i] = 0;
// for (int j = i + 1; j <= m; j++) {
// if (s[i] == s[j]) {
// if (j - i <= 2)
// dp[i][j] = 0;
// else
// dp[i][j] = dp[i + 1][j - 1];
// } else
// dp[i][j] = min(dp[i + 1][j] + cost[s[i] - 'a'], dp[i][j - 1] + cost[s[j] - 'a']);
// }
// }
for (int i = 1; i <= m; i++)
dp[i][i] = 0;
for (int len = 2; len <= m; len++) // 长度,从短到长
for (int i = 1; i + len - 1 <= m; i++) { // 起点
int j = i + len - 1; // 终点
if (s[i] == s[j]) {
if (j - i <= 2)
dp[i][j] = 0;
else
dp[i][j] = dp[i + 1][j - 1];
} else {
if (j - i <= 1)
dp[i][j] = min(cost[s[i] - 'a'], cost[s[j] - 'a']);
else
dp[i][j] = min(dp[i + 1][j] + cost[s[i] - 'a'], dp[i][j - 1] + cost[s[j] - 'a']);
}
}
printf("%d\n", dp[1][m]);
}
return 0;
}
关于起点从尾部开始递推的写法的原因
`dp[i][j] = min(dp[i+1][j], dp[i][j-1])`
由于`dp[i][i]`起点是i,而它是由起点 i + 1比较得来的,所以可以从尾部开始,那么第二层循环就是遍历长度(防止越界)
4、括号配对
题意:给出一串括号,要求输出添加括号最少,并使所有括号均有匹配的串
思路:
状态表示:dp[l][r]
表示向字符串中的i~j变成匹配的串的最少添加数量
状态计算:1、l与r匹配,dp[l][r] = dp[l+1][r-1]
,举个例子比如:[]([]
,如果直接按照前面这样计算dp[l][r]
,]([
的结果为3,但是实际答案却是1。可以先匹配[] + ([],也就是f[1,2]+f[3,5]。
遍历该区间的子区间,看是否有匹配的括号串,从而减少答案。dp[l][r] = dp[l][k] + dp[k+1][r]
。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, inf = 0x3f3f3f3f;
char s[N];
int f[N][N]; // f[i][j]代表将i~j个字符变成GBE的所有方案的集合的最小值
bool check(int a, int b)
{
if (s[a] == '[' && s[b] == ']' || s[a] == '(' && s[b] ==')') return true;
return false;
}
int main()
{
scanf("%s", s + 1);
int n = strlen(s + 1);
for (int len = 1; len <= n; len++)
for (int l = 1; l + len - 1 <= n; l++)
{
int r = l + len - 1;
if (len == 1) f[l][r] = 1;
else
{
f[l][r] = inf;
if (check(l, r)) f[l][r] = min(f[l][r], f[l + 1][r - 1]);
for (int k = l; k < r; k++)
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r]);
}
}
cout << f[1][n] << endl;
return 0;
}
附:记录输出最终的答案
pos[i][j] = k
表示括号串i-j中以第k个位置分开使得两部分匹配
pos[i][j] == -1
表示i和j两端点匹配
最长对称子串
暴力DP
设A[l][r]
为字符串 str[l:r]
的最长对称子串
对于第 l 位与第 r 位之间的字符串str[l:r]
,如果其是对称子串的话,则满足str[l] == str[r]
,且str[l+1: r-1]
为对称子串
初始条件
A[i][j] == 1
- > i == j
#include<isotream>
using namespace std;
vector<vector<int>> A(n,vector<int>(n,0));
int main()
{
string str = "";
getline(cin,str);
int n = str.size();
for(int i = 0;i < n;i++)
A[i][i] = 1;
int Max = 1;
for(int len = 1;len < n;len++) // 枚举子串长度
{
for(int l = 0;l + len< n;l++)
{
int r = l + len;
if(str[l] == str[r])
{
if(A[l+1][r-1] == r - l -1)// 如果该区间的最长对称子串等于总长度
{
A[l][r] = A[l+1][r-1] + 2;
if(A[l][r] > Max)
Max = A[l][r];
}
}
}
}
}
动态规划解法
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1010;
bool dp[N][N]; // dp[i][j]表示的是(i,j)字符串是否是回文串
string s;
int main() {
getline(cin, s);
int n = s.length();
int maxLen = 1;
for(int i = 0; i < n;i++) dp[i][i] = true; // 单个字符也是回文串
for(int L = 2; L <= n; L++) { // 遍历长度
for(int i = 0 ;i < n;i++) { // 遍历起点
int j = i + L - 1; // 终点
if(j >= n) break;
if(s[i] != s[j]) dp[i][j] = false;
else {
if(j - i <= 2) dp[i][j] = true;
else dp[i][j] = dp[i+1][j-1];
}
if(dp[i][j] && j - i + 1 > maxLen) maxLen = j - i + 1;
}
}
cout <<maxLen << endl;
return 0;
}