C++进阶课程第4期——动态规划
大家好,我是清墨,欢迎收看《C++进阶课程——动态规划dp》。
那么前几期都十分的顺利,我们直接开始~~~~
例题——抢金块
题目描述
地面上有一些格子,每个格子上面都有金块,但不同格子上的金块有不同的价值,你一次可以跳 S 至 T 步。
例如 S=2,T=4,你就可以跳 2 步、3 步或 4 步。
你从第一个格子起跳,必须跳到最后一个格子上,请你输出最多可以获得的金块的总价值。
输入格式
第 1 行是格子个数 n (n<=1000);
第 2 行是 S 和 T,保证 T 大于 S (2≤S<T≤10);
第 3 行是每个格子上的金块价值(1≤
≤10000)。
输出格式
输出最多可以获得的金块的总价值。如果不能走到第 n 个格子,输出 0.
样例
输入数据 1
10
2 3
4 5 8 2 8 3 6 7 2 9
输出数据 1
36
样例解释
跳 1、3、5、8、10 这些位置 ,总价值:4+8+8+7+9=36。
讲解
题目
我们可以先想像一下,想象一只青蛙在荷叶上跳跃,每片荷叶上有不同数量的昆虫,青蛙每次可以跳1-2片荷叶的距离,如何规划路线才能吃到最多的昆虫?
我们首先可以建立一下参数,n:荷叶总数(位置编号1-n),s/t:最小/最大跳跃步数,x[]:每个位置的昆虫数量。
这个时候我们可以定义一个数组,第个用来存储第
个位置能得到的最多昆虫,我们把这个数组的名字叫做
,即
代表到达位置i时的最大收获。
而数组的取名是这样得来的,Dynamic Programming。
我们可以思考一下,回到题目,我们要怎样求出呢?
我们发现是由
~
中的最大值决定的
所以可以敲出这样的代码:
for(int i=1;i<n;i++)
{if(dp[i]==0) {continue; }for(int j=s;j<=t;j++){if(i+j<=n){dp[i+j]=max(dp[i+j],dp[i]+x[i+j]); }}
}
这里就是动态规划的主体了。
而我们把形如的叫做动态规划中的动态转移方程:我们设计好一些关键信息,称为状态。推演的过程里面,往往会有一个初始的状态,然后我们从已知的状态推演出新的状态,一个状态推演结束之后,这个状态在后期就不需要改变了(如果改变,那就说明你构思出来的算法是不对的,要重新构思)。写程序之前,我们要能讲清楚状态是如何转移的,这可以用一个状态转移方程来描述。
动态规划算法的解题是把一个大问题变成一个小一点点的问题,小一点点的问题又变成了一个更小一点点的问题,到了这个问题足够小的时候,我们就能知道问题的答案,然后就可以解决大一点点的问题,然后再解决更大的问题。问题的规模可以变化,但是问题的性质是保持不变的。
动态规划与分治
如果说以前学过分治的同学,可以参考如下动态规划与分治的区别:
一、共同点
- 分治思想:两者均通过分解复杂问题为子问题来解决原问题。
- 递归与合并:均可能通过递归实现子问题的分解,并合并子问题的解。
二、核心区别
子问题独立性
- 分治法:子问题相互独立,无重叠(如归并排序的左右子数组排序)。
- 动态规划:子问题存在重叠,需存储中间解避免重复计算(如斐波那契数列)。
最优子结构
- 动态规划要求问题具有最优子结构(如最短路径问题),而分治法仅需子问题独立求解(如快速排序)。
实现方式
- 分治法通常使用纯递归,可能效率较低。
- 动态规划通过迭代或记忆化递归(如状态转移方程)优化效率。
代码
#include<bits/stdc++.h>
using namespace std;
int n,dp[1001],x[1001],s,t,ans;
int main()
{cin>>n>>s>>t;for(int i=1;i<=n;i++){cin>>x[i];}dp[1]=x[1];for(int i=1;i<n;i++){if(dp[i]==0) {continue; }for(int j=s;j<=t;j++){if(i+j<=n){dp[i+j]=max(dp[i+j],dp[i]+x[i+j]); }} }cout<<dp[n];return 0;
}
练习
题目
传球游戏
n 个同学围成一圈,编号为1到n(假设小蛮是 1 号)。游戏开始时球在小蛮手中,传球规则如下:
- 每次传球时,持球同学可将球传给左侧或右侧的任意一名同学12。
- 传球进行 m 次后停止,若球最终回到小蛮(1 号)手中,则视为一种有效传球方法12。
- 两种传球方法不同,当且仅当接球顺序的序列不同(例如
1→2→3→1
和1→3→2→1
视为两种方法)。
输入
两个整数 n 和 m(3 ≤ n ≤ 30,1 ≤ m ≤ 30),分别表示同学人数和传球次数。
输出
一个整数,表示传球 m 次后球回到小蛮手中的不同方法总数。
示例
当 n=3、m=3 时,有效方法为 1→2→3→1
和 1→3→2→1
,输出 2。
分析
我们可以进行一个分析:
假设只有4个人时,将四个人各自标记成A,B,C,D。
刚开始球可以传给B,C,D,所以传给B有1种可能,传给C也有1种可能,传给D也有1种可能。
那么,以此类推:
传球n次后传到A同学的可能数=传球n-1次后传到B同学的可能数+传球n-1次后传到D同学的可能数
即,同样的
,
。
那么我们可以逐层遍历。(特别的,)
只需要考虑边界就能解决了!
代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
long long a[3000][3000];
int main(){cin>>n>>m;a[1][0]=1;for(int j=1;j<=m;j++){for(int i=1;i<=n;i++){if(i>1&&i<n)a[i][j]=a[i-1][j-1]+a[i+1][j-1];else if(i==1)a[i][j]=a[n][j-1]+a[i+1][j-1];else a[i][j]=a[i-1][j-1]+a[1][j-1];}}cout<<a[1][m];return 0;
}