目录
(如果想看递归写法可以到我的记忆化递归里去看看记忆化递归_将递归程序记忆化-CSDN博客)
1.动态规划的介绍
是解决多阶段决策问题常用的最优化理论,该理论由美国数学家Bellman等人在1957年提出,用于研究多阶段决策过程的优化问题。
动态规划方法的原理就是把多阶段决策过程转化为一系列的单阶段决策问题,利用各个阶段之间的递推关系,逐个确定每个阶段的最优化决策,最终堆看出多阶段决策的最优化决策结果。
动态规划适合求解多阶段(状态转换)决策问题的最优解,决策的阶段可以随时间划分也可以随着问题的演化状态划分。问题都具有的一个性质就是“最优子结构”。
而动态规划的重点就是在于找出状态转移方程。
接下来我们看几道例题
2.动态规划的例题
第1道题 数字三角形
(如果想看递归写法可以到我的记忆化递归里去看看记忆化递归_将递归程序记忆化-CSDN博客)
数字三角形的洛谷链接:[USACO1.5] [IOI1994]数字三角形 Number Triangles - 洛谷
这道题问的就是怎样走可以数字和最大(一棵树);
这道题思路很简单,我们可以从这棵树的底部开始进行运算,比大小。
所以我们的动态转移方程就是:d[i][j] = a[i][j]+max(d[i+1][j],d[i+1][j+1]);//它等于它底下的节点中的最大值加上本身
最后代码:
#include <bits/stdc++.h>
using namespace std;
int d[1005][1005];
int a[1005][1005];
int n;
int main(){
cin >> n;
for (int i =1; i <= n+1; i++){
for (int j =1; j <= i; j++){
cin >> a[i][j];
}
}
for (int j =1; j <= n; j++){
d[n][j] = a[n][j];
}
for (int i =n-1; i >= 1; i--){
for (int j =1; j <= i; j++){
d[i][j] = a[i][j]+max(d[i+1][j],d[i+1][j+1]);
}
}
cout << d[1][1];
return 0;
}
第2道题最长公共子序列(模板)
注意子序列是不连续的(也可以连续,但不强求连续)
这道题思路并不是那么难;, A、B,2个字符串每一号位进行比较,如果它们这两个字符相等的话,我们就要让dp[i][j]=dp[i-1][j-1]+1,其中+1代表增加了一个公共子序列中的字符,然后再加上没有它们两个字符的时候的值。如果这两个字符不相等,那就取不包含第一个字符和不包含第二个字符的最长子序列最大值(长度)。
所以我们可以得出的状态转移公式:
if (a[i-1] == b[j-1]){
dp[i][j] = dp[i-1][j-1]+1;
}
else{
dp[i][j] = max(dp[i][j-1],dp[i-1][j]);
}
总体代码:
#include <bits/stdc++.h>
using namespace std;
int dp[10000][10000];
int main(){
int n ,m;
cin >> n >> m;
string a,b;
cin >> a >> b;
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
if (a[i-1] == b[j-1]){
dp[i][j] = dp[i-1][j-1]+1;
}
else{
dp[i][j] = max(dp[i][j-1],dp[i-1][j]);
}
}
}
cout << dp[n][m];
return 0;
}
第3道题 最长上升子序列
上面是洛谷的链接⬆️
这道题的总体思路:
dp[i]的意思是从0号位到i号位最长上升子序列(子序列的结尾是i号位)
进行循环构建出DP数组
状态转移公式:
if (a[j] < a[i]){
dp[i] = max(dp[i],dp[j]+1);
}
最后再用M算出整个数组中的最大值.
最后代码:
#include <bits/stdc++.h>
using namespace std;
int dp[50000];
int a[100000];
int main(){
int n;
cin >> n;
for (int i = 0; i <n; i++){
cin >> a[i];
}
int m = 0;
for (int i = 0; i <n; i++){
dp[i] = 1;
for (int j = 0; j <i; j++){
if (a[j] < a[i]){
dp[i] = max(dp[i],dp[j]+1);
}
}
if (dp[i] > m){
m = dp[i];
}
}
cout << m;
return 0;
}
第4道题最大子段和
( ̄y▽ ̄)╭最大子段和 - 洛谷
这道题的思路很简单,dp[i]代表着从0号位到第i号位结尾为第i号位的最大子段和是多少
那就可以分为(取前面的和)和(不取前面的和)
在求一个最大值中的最大值就OK了
状态转移公式:
dp[i] = max(dp[i-1]+a[i],a[i]);
总体代码:
#include <bits/stdc++.h>
using namespace std;
long long dp[500005];
long long a[500000];
int main(){
int n;
cin >> n;
for (int i = 0; i <n; i++){
cin >> a[i];
}
long long m = -2e9;
for (int i = 0; i <n; i++){
dp[i] = max(dp[i-1]+a[i],a[i]);
m = max(m,dp[i]);
}
cout << m;
return 0;
}
背包系列问题
01背包
0< N,M ≤1000
0< vi,pi ≤1000
我们可以分成几种情况
dp[i][j]代表了1~i个东西和可装重量为j的书包
dp[i-1][j-v[i]]代表了如果总重量加入了我们第i个物品(也就是放了),最终可以得到多少价值的东西.
转移公式:
if (j < v[i]){
dp[i][j] = dp[i-1][j];
}
else{
dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
}
最后代码:
#include <bits/stdc++.h>
using namespace std;
int dp[1005][1005];
int main(){
int n,m;
cin >> n >> m;
int v[1005],w[1005];
for (int i = 1; i <= n; i++){
cin >> v[i] >> w[i];
}
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
if (j < v[i]){
dp[i][j] = dp[i-1][j];
}
else{
dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
}
}
}
cout << dp[n][m];
return 0;
}
完全背包
疯狂的采药:疯狂的采药 - 洛谷
完全背包和01背包的不同在于一个东西它可以有无限个,也就是说你可以取无限次,只要你背包还有容量那我们就要把i-1改成i因为这一个东西它可以取很多次.
在这里我们用的是一维数组,我们要倒过来循环
转移方程:(只不过这里的写法不同,和上面的判断条件意思是一样的)
dp[j]= dp[j];
if (j >= w[i]){
dp[j] = max(dp[j],dp[j-w[i]]+c[i]);
}
所有代码:
#include <bits/stdc++.h>
using namespace std;
long long w[10000005],c[10000005];
long long dp[10000005];
long long m,n;
int main(){
cin >> m >>n;
for (long long i = 1; i <= n; i++){
cin >> w[i] >> c[i];
for (long long j = 1; j <= m; j++){
dp[j]= dp[j];
if (j >= w[i]){
dp[j] = max(dp[j],dp[j-w[i]]+c[i]);
}
}
}
cout << dp[m];
return 0;
}