思路:多阶段决策,每个阶段做出决策,全部过程为决策序列,将动态规划原问题分解成多个相互重叠的子问题,自底向上的求解最优值
DP性质:判断是否符合动态规划的条件,满足:
解题步骤:
一维问题子问题的划分
二维问题的划分
例1: 有n块石头编号为0-n-1
一只青蛙从0开始跳到n-1石头的位置,每次只能向右跳,且如果该蛙在第i块石头上,他可以跳到ai个位置,a[]中保存了每次可以跳的步数 问:该蛙能否跳到第n-1块石头?
算法思想:设f[j]表示能否跳到j,为布尔型
F[j] = 1 {F[j-1]==1 && i+a[i]>=j} //判断当前所在位置是否跳到,且是否还能跳到第j个石头、
F[j] = 0 else
其中i+a[i]>=j为最后一步的距离, F[j-1]为上一步的状态。
每次设目标为j,从0开始,每次移动将当前位置加上a[i]中跳的步数,判断能否达到目标
- bool CanJump(int A[], int n){
- bool F[n]; //保存状态
- F[0] = true;
- for(int j = 1; j
//确定目标 - f[j] = false;
- for(int i = 0;i
- if(F[j-1] && i+a[i] >=j ) //能跳到目标
- F[j] = true; //则将当前位置置true
- }
- }
- return F[n-1]; //返回最后一块石头的状态
- }
例2:求 数列中最大连续子序列之和
状态转移方程:dp[i] = max{dp[i-1]+nums[i],dp[i-1]};
设当前位置为i (i>1),且之前的序列之和dp[i-1]的数值 与 加上当前位置的数值只和大小, 选择较大的数值作为当前最大连续序列之和
- int maxSubArray(int nums[], int n){
- int maxsum = nums[0];
- int DP[n];
- for(int i = 0; i < n; i++){
- if(nums[i-1] > 0){
- dp[i] = nums[i] + dp[i-1];
- if(dp[i] < dp[i-1]){
- dp[i] = dp[i-1];
- }
- }
- }
- return dp[n];
- }
例3. 硬币划分,有面值2,5,7元的硬币,买一本书要27,用最少的硬件组合正好付清,且不需要找零。
算法思想:确定状态,开辟一个解空间数组保存最优值,需考虑前面的两个状态来确定当前最优解。一定有最后一枚硬币a[k],使得之前所有硬币加起来有27-a[k]

设金额为N,从N=27开始迭代,每次金额减去当前选择硬币面值大小,此时硬币数量+1
所以状态转移方程:F[n] = min{F[n-a[0]]+1, F[n-a[1]]+1, F[n-a[2]]+1}
- int MinCoin(int N, int a[],int m){
- int dp[N];
- dp[0] = IntMax; //设置最大值,最用找出最小解
- for(int i=1; i <=N;i++){ //遍历所有金额时的硬币数量
- for(int j = 0; j
//遍历硬币 - if((dp[i-a[j]]+1 < dp[i]) && (i-a[j]>0))
- dp[i] = dp[i-a[j]]+1;
- }
- }
- if(dp[N] == IntMax){
- return -1;
- }
- return dp[N];
- }
二维问题
例1:给定网络,从左上角(0,0)出发,只能向左或向下移动,问有多少方法能走到右下角
算法思想:子问题设dp[i][j]为当前有多少方式走到(i,j),则对任意的位置(i,j)的状态转移方程为:
dp[i][j] = f[i-1][j]+f[i][j-1];
可以想象如果是从两个不同的平行宇宙达到该位置,则有上面和左边两个不同的路径
在初试位置设dp[0][0]=1,即计算左边位置已有的方法总和加上上面位置已有的方法数量总和为当前位置的总和。
- int Ways(int m,int n){
- int dp[m][n];
- dp[0][0] = 1; //初始位置路线数量为1
- for(int i=1;i<=M;i++){ //对网格地图进行遍历
- for(int j = 1;j<=N;j++)
- dp[i][j] = dp[i-1][j]+dp[i][j-1];
- }
- return dp[m][n];
- }
例2:LCS最长公共子序列
两个字符串分别是“ABCBDAB”和“BDCABC”,其中最长公共子序列长度为4,则任意两个字符串,求其中最长公共子序列长度
算法思想:若s1[i]==s2[i],有当前相等的长度加1,即将之前的状态长度更新,则状态转移方程为:
dp[i][j] = d[i-1][j-1]+1;
否则比较之前两个状态中较长的一个作为当前最优解
dp[i][j] = max{dp[i-1][j],dp[i][j-1]} s1[i]!= s2[j]
状态转移方程: dp[i][j] = 0 (i=0 || j=0)
dp[i][j] = dp[i-1][j-1] + 1; s1[i] = s2[j]
dp[i][j] = max{dp[i-1][j],dp[i][j-1]} s1[i]!= s2[j]
且若相等则当前状态置1,否则,s1中较长状态置2,s2中较长状态置3
从后向前递推,判断s1倒数第i个和s2倒数第j个元素向前地推,找出最长序列状态
再通过状态数组递归求解最长公共秩序列长度
- const int maxsize = 10000;
- void LCSLength(int s1[], int s2[], int m, int n){
- int dp[maxsize][maxsize]; //最长公共子序列长度
- int b[maxsize][maxsize]; //s1,s2子序列长度比较状态
- int i,j;
- for(i = 1;i<=m; i++){
- for(j = 1;j<=n;j++){
- if(s1[i] == s2[j])
- dp[i][j] = dp[i-1][j-1]+1; //之前公共长度+1
- b[i][j] = 1;
- else //最长公共子序列为 s1前i-1个和s2前j-1个最长公共子序列,确定较长的状态
- if(dp[i-1][j] > dp[i][j-1])
- dp[i][j] = dp[i-1][j];
- b[i][j] = 2;
- else
- dp[i][j] = dp[i][j-1];
- b[i][j] = 3;
- }
- }
- }
- viod LCS(int i,int j){
- if(i==0 || j==0) return;
- if(b[i][j] == 1){
- LCS(i-1,j-1);
- printf("%d",s[i]);
- }else if(b[i]lj] == 2){
- LCS(i-1,j);
- }else{
- LCS(i,j-1);
- }
- }
例3:0-1背包问题
背包问题:有M个价值各不同的珠宝,小偷偷每个珠宝的时间也各不同,要在保安发现之前赶紧把珠宝尽可能多地装进背包,同时也必须保证背包内珠宝总价值最大
设:背包大小为M[][],t时刻偷窃珠宝的价值为V[100],每个珠宝偷窃的时间为time[t]
t时刻偷窃第i个珠宝,当前时间为t,则对当前珠宝进行判断,
设上次偷窃完背包内价值加上本次这个珠宝价值v[i]为: m[i-1][ t-time[i] ],
如果不偷窃该珠宝,则为: m[i-1][t]
状态转移方程:M[i] [j]= max{ v[i]+m[i-1][j-time[i] , m[i-1][t]}
- int TotalTime, num; //设置总时间和珠宝数量
- int value[100],time[100],m[1000][1000];
- int main(){
- printf("输入总时间和珠宝数量\n")
- scanf("%d,%d",&TotalTime,&num);
- printf("输入每个珠宝的时间和珠宝价值\n")
- for(int i = 0; i
- scanf("%d,%d",&time[i],&value[i]);
- printf("\n");
- }
- for(int i = 0; i
- for(int t=TotalTime;t>0;t--){ //剩余时间
- if(time[t]
//当前珠宝偷窃时间足够 - int value1 = m[i-1][t-time[t]+v[i];
- int value2 = m[i-1][t]; //如果不采集,时间不变
- m[i][t] = (value1>value2 : value1?value2);
- }
- }
- }
- printf("偷窃总价值为:%d",m[num][TotalTime]);
- return 0;
- }
例4:路径之和最小
给定三角形数字阵列,选择一条自顶向下的路径,使得沿途所有数字之和最小
2
3,4
6,5,7
4,1,8,3 其中最小路径之和为:2+3+5+1 = 11
算法思想:自底向上计算最优解,从最下面一行元素开始,每次移动可以向上移动,也可以向左右移动,每次移动取三个状态的最小值加上当前位置元素作为当前最优解
状态转移方程:dp[i][j] = min{dp[i-1][j] , dp[i-1][j-1] , dp[i-1][j+1]}+a[i][j];
从最后一行往上回溯遍历,选择上一行相邻的每个数据的状态加上当前数值大小
- int MinDP(int a[][], int n){
- int dp[maxsize][maxsize];
- for(int i = n; i>0;i--){
- dp[n][j] = a[n][i];
- for(int j = 0; j
- dp[i][j] = math.min(dp[i-1][j],dp[i-1][j+1],dp[i-1][j-1])+a[i][j];
- }
- }
- int result = dp[1][1]; //从最下面加到一个元素为最终结果
- return result;
- }
例5:某地有未填平的海域,需要n块石头填海,每块石头消耗体力为k[i],当前体力值为C,每块石头的体积为Vi,输入三个整数:V(需填的体积),n(石头数),C(体力乘积)
第二行输入每块石头体积和消耗的体力
若能填平输出剩余体力,否为输出“Impossible!”
算法思想:现在所有石头体积中将最大的石头填平海面,再选出所有方案中体力消耗最小的状态作为最优解。
状态转移方程:f[j] = max{f[j], f[j-k[i]]+v[i]}。j为当前剩余体力
对每块石头可以选择填或不填,填入则海面剩余体积要减去石头的体积同时剩余体力值要减去当前石头消耗的体力
- #define MaxSize 10000;
- int v[MaxSize], k[MaxSize],f[MaxSize];
- int main(){
- int n, C ,V;
- scanf("%d,%d,%d",&n,&C,&V);
- for(int i = 1; j<=n; i++){
- scanf("%d,%d",&v[i],&k[i]);
- }
- int answer = 100000; //取最大值作为求最小值的初始解
- for(int i = 1; i<=n;i++){
- for(int j = C;j > 0 && j>=k[i] ;j--){ //体力从C开始,且保证石头能搬得动
- f[j] = math.max(f[j], f[j-k[i]]+v[i]);
- if(f[j] >= V){ //当前填完的体积大于还需填平的体积
- answer = min{answer, j}; //取最小体力作为结果
- }
- }
- }
- if(answer > C) //超过初始体力
- printf("Impossible!");
- printf("%d",c-answer); //输出消耗体力
- return 0;
- }
例6:租用游艇问题
有N个游艇出租站1,2,…,n。游客可在这些游艇出租站租用游艇,并在下游任何一个位置归还游艇。游艇出租站i到j之间的租金为r(i,j),计算从出租站1到n租船需要的最少租金。
状态转移方程:dp[i][j] = min{dp[i][k] + dp[k+1][j] , dp[i][j]}
- #define MaxSize 10000;
- void dp(int n){
- int temp;
- int dp[MaxSize][MaxSize], r[MaxSize]; //dp为1-N不同出租站的租用状态,r为租金
- for(int i=1;i<=n;i++){
- for(int j=1;j<=n;j++){
- scanf("%d",&r[i][j]); //r[1][n]=Max
- dp[i][j] = r[i][j];
- }
- }
- for(int r = 2; r<=n;r++){ //从第2个站台开始作为归还点,到最后一个进行遍历
- for(int i = 1; i < n-r+1; i++){ //从开始站台到归还点之间的站台再次租用
- int j = r+i-1; //归还点+租用站台数量作为租用次数
- for(int k = i; k < = j; k++){
- temp = dp[i][k] + dp[k+1][j];
- if(temp < dp[i][j])
- dp[i][j] = temp;
- }
- }
- }
- return dp[1][n];
- }
-
相关阅读:
leetcode 496: 下一个更大元素 I
关于linux多线程fork的理解和学习
利用Redis实现向量相似度搜索:解决文本、图像和音频之间的相似度匹配问题
uniapp和vue3+ts创建自定义下拉选择框组件
java计算机毕业设计springboot+vue游戏道具管理系统
git原理浅析
复习Day03:数组part03:76 . 最小覆盖子串、438. 找到z字符串z中所有字母异位词
练习 4 Web [MRCTF2020]Ez_bypass
线性代数学习笔记11-1:总复习Part1(CR分解、LU分解、QR分解)
5. 最长回文子串
-
原文地址:https://blog.csdn.net/qq_37504771/article/details/126952481