前言和总结
在学习算法的途中突然遇到了区间dp算法 所以自己参考其它大佬的博客争对性的做了几道写下此篇博客。
区间dp给我感慨良多 虽然每道题都是这样固定的模板 但是每做一道题还是会思考良久 总是感觉有一点东西没有想清楚 做完也会让我怀疑这短短的代码究竟能不能成功 过了之后又会想为什么这么短的代码就可以解决看似很复杂的问题(可能这就是算法的魅力吧!)
下面就是区间dp的模板
区间dp解决看似区间的问题,一般会有三个for循环 :第一个for循环是枚举长度 第二个for循环在第一个的基础上枚举起点 第三个for循环再枚举在枚举的区间中的分割点 从而实现让大区间分解成小区间 小区间又分解成小区间 最后分解成点或者已知数据的过程 (太难了吧!)
for(int len = 1;len<=n;len++){//枚举长度
for(int j = 1;j+len<=n+1;j++){//枚举起点,ends<=n
int ends = j+len - 1;
for(int i = j;i<ends;i++){//枚举分割点,更新小区间最优解
dp[j][ends] = min(dp[j][ends],dp[j][i]+dp[i+1][ends]+something);
}
}
}
石子归并
链接:https://vjudge.net/problem/51Nod-1021
题目描述:
N堆石子摆成一条线。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价。计算将N堆石子合并成一堆的最小代价。
例如: 1 2 3 4,有不少合并方法
1 2 3 4 => 3 3 4(3) => 6 4(9) => 10(19)
1 2 3 4 => 1 5 4(5) => 1 9(14) => 10(24)
1 2 3 4 => 1 2 7(7) => 3 7(10) => 10(20)
括号里面为总代价可以看出,第一种方法的代价最低,现在给出n堆石子的数量,计算最小合并代价。
输入:
第1行输入一个数N(2 <= N <= 100) 第2 - N+1行每行输入一个数,分别表示N堆石子的数量(1 <= Ai <= 10000)
输出:
输出最小合并代价
示例输入:
4
1
2
3
4
示例输出:
19
分析:
首先设置dp[i][j] 然后规定其为第i堆石子到第j堆石子的最小合并代价。然后利用区间dp的模板代入,找出转移方程
dp[j][ends]=min(dp[j][ends],dp[j][k]+dp[k+1][ends]+sum[ends]-sum[j-1]);
在j到ends中用k来分割 所以dp[j][ends]最小可以是自己也可以是前一堆dp[j][k]和后一堆dp[k+1][ends]想加再加两堆合并的数量 即可
#include <iostream>
#include <cstring>
using namespace std;
int num[105];
int sum[105];
int dp[105][105];
int main()
{
int n;
cin>>n;
memset(dp,0x3f3f,sizeof(dp));
for(int i=1;i<=n;i++){
cin>>num[i];
sum[i]=num[i]+sum[i-1];
dp[i][i]=0;
}
for(int i=1;i<=n;i++){
for(int j=1;j+i-1<=n;j++){
int ends=j+i-1;
for(int k=j;k<ends;k++){
dp[j][ends]=min(dp[j][ends],dp[j][k]+dp[k+1][ends]+sum[ends]-sum[j-1]);
}
}
}
cout<<dp[1][n]<<endl;
return 0;
}
括号匹配
链接:http://poj.org/problem?id=2955
题目描述:
给你很多小括号和中括号 让你找出最多匹配的括号数量
输入:
输入测试文件将包含多个测试用例。每个输入测试用例由仅包含字符的单行(,),[,和]; 每个输入测试的长度在 1 到 100 之间,包括 1 和 100。文件结尾由包含单词“end”的行标记,不应处理。
输出:
对于每个输入案例,程序应该在一行上打印最长可能的正则括号子序列的长度。
样例输入:
((()))
()()()
([]])
)[)(
([][][)
end
样例输出:
6
6
4
0
6
分析:
首先dp[i][j]代表第i个符号到第j个符号最大的匹配数量 这里就多了一个if条件 我们很清晰知道s[i]和s[j]如果匹配那么dp[i][j]=dp[i+1][j-1]+2可以成为改变状态的条件 还有一个就是dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]) 就是i到j之间可以是i到k 再k+1到j的数量之和
#include <iostream>
#include <cstring>
using namespace std;
int dp[105][105];
int main()
{
string s;
while(cin>>s){
if(s=="end")break;
memset(dp,0,sizeof(dp));
int n=s.size();
for(int i=1;i<=n;i++){
for(int j=1;j+i-1<=n;j++){
int ends=j+i-1;
for(int k=j;k<ends;k++){
if(s[ends-1]-s[j-1]==1||s[ends-1]-s[j-1]==2){
dp[j][ends]=max(dp[j][ends],dp[j+1][ends-1]+2);
}
dp[j][ends]=max(dp[j][ends],dp[j][k]+dp[k+1][ends]);
}
}
}
cout<<dp[1][n]<<endl;
}
return 0;
}
[CQOI2007]涂色PAINT
链接:https://ac.nowcoder.com/acm/problem/19909
题目描述:
假设你有一条长度为5的木版,初始时没有涂过任何颜色。你希望把它的5个单位长度分别涂上红、绿、蓝、绿、红色,用一个长度为5的字符串表示这个目标:RGBGR。 每次你可以把一段连续的木版涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色。
例如第一次把木版涂成RRRRR,第二次涂成RGGGR,第三次涂成RGBGR,达到目标。 用尽量少的涂色次数达到目标。
输入描述:
输入仅一行,包含一个长度为n的字符串,即涂色目标。
字符串中的每个字符都是一个大写字母,不同的字母代表不同颜色,相同的字母代表相同颜色。
输出描述:
仅一行,包含一个数,即最少的涂色次数。
输入:
RGBGR
输出:
3
分析:
首先dp[i][j]的意思是第i块木板到第j块木板染色需要的最少涂色次数
其中一个特殊条件就是当第i块和第j块的木板色相同时dp[i][j]=min(dp[i+1][j],dp[i][j-1]);就默认少刷一块 而不能是减一次因为这个数字还没更新;
最基本的状态转移就是dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])即大区间等于前半区间和后半区间相加
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
int dp[55][55];
int main()
{
string s;
cin>>s;
memset(dp,0x3f3f3f3f,sizeof(dp));
int s_size=s.size();
for(int i=1;i<=s_size;i++){
dp[i][i]=1;
}
for(int len=2;len<=s_size;len++){
for(int j=1;j+len-1<=s_size;j++){
int ends = j+len - 1;
if(s[j-1]==s[ends-1]){
dp[j][ends]=dp[j][ends]-1;
}else{
for(int i=j;i<ends;i++)
dp[j][ends] = min(dp[j][ends],dp[j][i]+dp[i+1][ends]);
}
}
}
cout<<dp[1][s_size]<<endl;
return 0;
}
Multiplication Puzzle
链接:http://poj.org/problem?id=1651
题目描述:
给你n个数字,要求不能删除两端点的数字,然后删除其他数字的代价是该数字和左右相邻数字的乘积,问把数字(除端点)删完后的最小总代价。
输入:
输入的第一行包含卡片数量 N (3 <= N <= 100)。第二行包含 1 到 100 范围内的 N 个整数,用空格分隔。
输出:
输出必须包含一个整数 - 最低分数。
样本输入:
6
10 1 50 50 20 5
样本输出:
3650
分析:
首先dp[i][j]代表第i-1张卡片到第j张卡片的最低分数(有点不同),然后我们就会找出以下方程
dp[j][ends]=min(dp[j][ends],dp[j][k]+dp[k+1][ends]+num[j-1]*num[k]*num[ends]);
为什么dp[i][j]不是i到j张卡片的最低分数呢 下面解释如果用就会如第二张图在状态转移方程中合并时会把中间的卡片删除 但是会有两张又会有可能性所以不可取。
#include <iostream>
#include <cstring>
using namespace std;
int num[105];
int dp[105][105];
int main()
{
int n;
cin>>n;
memset(dp,0x3f3f3f3f,sizeof(dp));
for(int i=1;i<=n;i++){
cin>>num[i];
dp[i][i]=0;
}
for(int i=1;i<=n;i++){
for(int j=2;j+i<=n+1;j++){
int ends=j+i-1;
for(int k=j;k<ends;k++){
dp[j][ends]=min(dp[j][ends],dp[j][k]+dp[k+1][ends]+num[j-1]*num[k]*num[ends]);
}
}
}
cout<<dp[2][n]<<endl;
return 0;
}