区间DP定义
顾名思义:区间dp就是在区间上进行动态规划,求解一段区间上的最优解。主要是通过合并小区间的 最优解进而得出整个大区间上最优解的dp算法。
实现思路
- 既然是在区间里进行DP:
- 以‘区间长度’作为阶段
- 状态为某区间内的最有解,即将长度为1的元区间作为DP的最小状态。使用 d p [ l , r ] dp[l, r] dp[l,r] 描述每一个维度。
典型例题
删除字符串
题目描述
给出一个长度为n的字符串,每次可以删除一个字母相同的子串,问最少需要删多少次。 数据规模:n <= 500
输入格式
第1行:1个整数,表示字符串的长度
第2行:n个字符的字符串
输出格式
第1行:1个整数,表示答案
样例
样例输入
5
a b a c a abaca abaca
样例输出
3
样例解释:
step 1:删除 b b b 得到
a a c a aaca aaca
step 2 : 删除 c c c 得到
a a a aaa aaa
step 3 : 因为 a a a aaa aaa 为相同的子串,既可以一次删除。
空 串 空串 空串
分析
此题即为典型的区间DP题,根据题目可以设以 d p [ l , r ] dp[l, r] dp[l,r] 是为 l l l 到 r r r 区间删除完字符串的最小次数,可分两种情况讨论:
- 一般情况下, d p [ l , r ] dp[l,r] dp[l,r] 由长度可以通过 d p [ l + 1 , r ] dp[l + 1, r] dp[l+1,r] 或 d p [ l , r − 1 ] dp[l , r - 1] dp[l,r−1]增加一个字符得到,此时取两者之间的最小值。
- 枚举 k ∈ ( l , r ) k ∈ (l, r) k∈(l,r), 用 k k k 作为决策点,如果 c [ l ] = c [ k ] c[l] = c[k] c[l]=c[k] 即可以通过 d p [ l ] [ k − 1 ] + d p [ k ] [ r ] dp[l][k - 1] + dp[k][r] dp[l][k−1]+dp[k][r]直接得到, 注意此处不需要加一, 因为 c [ l ] c[l] c[l] 和 c [ k ] c[k] c[k]是相同字符,所以可以直接删除,而在计算 c [ l ] c[l] c[l] 和 c [ k ] c[k] c[k]时,已经加一所以,不需要加一!
不难得到状态转移方程:
- 一般情况:
d p [ l ] [ r ] = m i n ( d p [ l + 1 ] [ r ] + 1 , d p [ l ] [ r − 1 ] + 1 ) ; dp[l][r] = min(dp[l + 1][r] + 1, dp[l][r - 1] + 1); dp[l][r]=min(dp[l+1][r]+1,dp[l][r−1]+1); -
k
∈
(
l
,
r
)
k ∈ (l, r)
k∈(l,r), 如果
c
[
l
]
=
c
[
k
]
c[l] = c[k]
c[l]=c[k] :
d p [ l ] [ r ] = m i n ( d p [ l ] [ r ] , d p [ l ] [ k − 1 ] + d p [ k ] [ r ] ) ; dp[l][r] = min(dp[l][r], dp[l][k - 1] + dp[k][r]); dp[l][r]=min(dp[l][r],dp[l][k−1]+dp[k][r]);
AC代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n, dp[505][505];
char c[505];
int main() {
scanf("%d", &n);
scanf("%s", c + 1);//从一号位开始存
// for(int i = 1;i <= n; i++) {
// printf("%c", c[i]);
// }
memset(dp, 0x3f, sizeof(dp));//初始化
for (int i = 1; i <= n; i++) dp[i][i] = 0;
for (int len = 2; len <= n; len++) {//阶段 len -> 长度
for (int l = 1; l <= n - len + 1; l++) {//状态 l -> 左端点
int r = l + len - 1;//状态 r -> 右端点
dp[l][r] = min(dp[l + 1][r] + 1, dp[l][r - 1] + 1);//一般情况
for (int k = l; k <= r; k++) {//决策 k -> 划分点
if (c[l] == c[k])//字符相同
dp[l][r] = min(dp[l][r], dp[l][k - 1] + dp[k][r]);
}
}
}
printf("%d\n", dp[1][n] + 1);
return 0;
}
注意
区间DP的循环顺序应该为
for(阶段)
for(状态)
for(决策)