今天给大家带来算法系列-动态规划的区间dp问题,这类问题的核心在于能否把区间分为不同状态进行组合。区间问题适合新手理解动态规划的基本思想。
1.
问题描述
小飞最近喜欢上了打地鼠,给定一个长度为 n的数组 a,ai 表示第 i个位置上的地鼠所带有的分数。
小飞最初处于某一位置,然后左右移动开始打两边的地鼠,经过一个位置时,若此处的地鼠未被打过,则强制打该地鼠,任一位置的地鼠仅能被打一次。在两个相邻位置间移动的耗时为 1,打地鼠的过程瞬间完成,不耗时。打地鼠所获得的分数满足以下条件:若本次打地鼠的移动方向与上一次打地鼠的移动方向相反,则获得的分数为地鼠自身所带有的分数,否则获得的分数为地鼠自身所带有分数的一半(向下取整);起始点的地鼠被打后获得分数为地鼠自身全部分数。
小飞可以从任一位置开始打地鼠,直到 n 个位置的地鼠都被打过后游戏结束。最终的得分 S′S′ 等于小飞打地鼠获得的分数 S减去整个过程耗时 T的 10 倍,即 S′=S−T×10。
请给出小飞能得到的最大的 S′ 的值。
输入格式
第一行一个整数 n。
第二行 n个整数 a1,a2,...,an。
输出格式
输出一个整数 S′。
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e3+3;
ll dp[N][N][2];
int a[N];
int main()
{
int n;cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
dp[i][i][1]=a[i];
dp[i][i][0]=a[i];
}
for(int len=2;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
dp[i][j][0]=max(dp[i+1][j][0]+dp[i][i][0]/2-10,dp[i+1][j][1]+dp[i][i][0]-(len-1)*10);
dp[i][j][1]=max(dp[i][j-1][1]+dp[j][j][1]/2-10,dp[i][j-1][0]+dp[j][j][1]-(len-1)*10);
}
}
cout<<max(dp[1][n][1],dp[1][n][0])<<endl;
return 0;
}
这道题的有两个关键的地方:
1.因为题目要求n个地鼠都被打过才结束,所以对于每个区间来说,只有最后到达端点之后,才完成。也就是说对于一区间来说都是从端点处开始遍历。
2.对于状态转移方程来说,因为都是从端点处开始转移,所以分向左和向右两个方向,分别进行转移。
问题描述
小蓝去蓝桥工厂打工赚钱,蓝桥工厂中有 n个仓库,仓库的编号从 1 到 n。同时仓库按环形排列,n 号仓库后面的仓库为 1号仓库,每个仓库都有其一个对应工作酬劳 ci(1≤i≤n)。
现在小蓝可以选择长度为 m 的仓库打工,已经获得过报酬的仓库不能再次获得报酬。同时小蓝需要上交每段连续仓库的第一个仓库的工资给小红。例如小蓝只选择 77 号仓库打工,就要上交 7号仓库的酬劳。如果现在有 7个仓库,小蓝选择 (7→1),(2→3→4)号仓库,总计 5 个仓库,小蓝需要上交 7 号仓库,2 号仓库的酬劳。
问你,小蓝该如何选择长度为 m 的仓库,才能使得自己获得的酬劳最大。
输入格式
输入第一行,包含 22 个正整数 n,mn,m。
接下来 nn 行,每行包含 11 个正整数,表示 cici。
输出格式
输出一个正整数,表示小蓝能获得到的最大酬劳。
#include <bits/stdc++.h>
using namespace std;
const int N=2e3+7;
long long dp[N][N][2];
int a[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
memset(dp,-0x3f,sizeof(dp));
dp[1][0][0]=0;
dp[1][1][1]=0;
for(int i=1;i<=n;i++){
dp[i][0][0]=dp[i-1][0][0];
for(int j=1;j<=m;j++){
dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]);
dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j-1][1]+a[i]);
}
}
long long ans=max(dp[n][m][1],dp[n][m][0]);
memset(dp,-0x3f,sizeof(dp));
dp[1][0][0]=0;
dp[1][1][1]=a[1];
for(int i=2;i<=n;i++){
dp[i][0][0]=dp[i-1][0][0];
for(int j=1;j<=m;j++){
dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]);
dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j-1][1]+a[i]);
}
}
ans=max(ans,dp[n][m][1]);
cout<<ans<<endl;
return 0;
}
这道题是我目前接触的最难理解的一道区间dp的问题,关键难点就是如何构造dp数组,以及怎么理解把环形数组变成线性的表达形式。
1.dp[j][j][2]表示当前在第i个位置,选择了j个,1表示当前在所选区间内,0表示当前不在所选区间内。
2.将环形区域的遍历线性化特别创新。首先环形部分i从2遍历,同时dp[1][1][1]=a[1],而不是0,充分考虑了从2开始,遍历回1的过程。
这道题有难度,需要大家多多理解,相信对大家的提升还是十分大的。
好了,今天的分享就到这里,希望大家可以多多关注博主,后续也将为大家带来更多的算法题目分享。