- 2022tysc1451 的博客
1.DP学习笔记
- @ 2025-3-15 16:46:34
C++DP的学习笔记
一、动态规划基础概念
定义:动态规划(Dynamic Programming,简称 DP)是一种解决多阶段决策问题的数学优化方法。它将原问题分解成若干个子问题,通过解决子问题并将结果保存下来,避免重复计算,提高算法效率。
二、适用问题特点
1.最优子结构:问题的最优解所包含的子问题的解也是最优的,局部最优解能决定全局最优解。
2.子问题重叠:子问题被重复求解,如斐波那契数列中,计算F(n)时,F(n-1)和F(n-2)等子问题会被多次计算。
三、动态规划解题步骤
1.确定 dp 数组及下标的含义:明确dp数组用来存储什么信息,以及数组下标的意义。例如在背包问题中,dp[i][j]可能表示前i个物品放入容量为j的背包中所能获得的最大价值。
2.确定状态转移方程:这是动态规划的关键,找到如何通过已知子问题的解推导出当前问题的解。比如在 01 背包问题中,dp[i][j] = max(dp[i-1][j-w[i]] + c[i], dp[i-1][j])。
3.dp 数组初始化:根据问题的边界条件,给dp数组赋初值。如在 01 背包中,dp[0][j] = 0表示没有物品时,无论背包容量多大,价值都为 0。
4.确定遍历顺序:确定是先遍历物品还是先遍历背包容量等,不同的问题有不同的合适遍历顺序。如 01 背包通常先遍历物品,再遍历背包容量,且背包容量要倒序遍历。
四、动态规划常见类型
1.斐波那契数列类型:包括斐波那契数列、爬楼梯、最小费用爬楼梯、泰波那契数、打家劫舍等。
2.矩阵类型题:最小路径和、不同路径、下降路径最小和、最大正方形等。
3.背包问题:01 背包、01 背包滚动数组、分割等和子集、零钱兑换、一和零等。 4.子序列问题:最长上升子序列、最长增长子序列、最长连续递增子序列、最长公共子序列、最长回文子串、编辑距离、单词拆分等。
5.股票问题:买卖股票最佳时机系列,包括买卖一次、买卖多次、买卖两次、买卖多次有手续费、买卖多次有冻结时间等。
五、动态规划与其他算法思想的区别
与分治思想
相似点:都能将问题分解为子问题求解。 不同点:动态规划解决的问题有重复子问题,且用于求解最优化问题;分治问题不一定有重复子问题,也不一定是最优化问题。
与贪心思想
相似点:都能分解为子问题求解。 不同点:贪心基于最优化策略,由局部最优推全局最优,需证明策略正确性;动态规划求得的一定是最优解,无需证明。
题型
一维dp
1.dp求最大子序列和:
例:输入n(n<=100000),和n个数,求序列中的最大子段和。
#include<bits/stdc++.h>
using namespace std;
int n,a,f[100001],maxn;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a;
if(i==1)f[1]=a;
else f[i]=max(f[i-1]+a,a);
maxn=max(maxn,f[i]);
}
cout<<maxn;
}
2.dp求最大子序列积:
输入一个整数n,和一个序列a,求它的最大子序列和。
#include<bits/stdc++.h>
using namespace std;
int n,a,f[100001],g[100001],maxn;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a;
if(i==1)f[1]=g[1]=a;
else{
f[i]=max(f[i-1]*a,max(g[i-1]*a,a));
g[i]=min(g[i-1]*a,min(f[i-1]*a,a));
}
maxn=max(maxn,f[i]);
}
cout<<maxn;
}
3.dp求最长不下降子序列
#include<bits/stdc++.h>
using namespace std;
int n,a[100001],f[100001],maxn;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
f[i]=1;
for(int j=1;j<i;j++){
if(a[j]<=a[i])f[i]=max(f[i],f[j]+1);
}
maxn=max(maxn,f[i]);
}
cout<<maxn;
}
二维dp
1.最长公共子序列
#include<bits/stdc++.h>
using namespace std;
int f[1001][1001];
string s1,s2;
int main(){
cin>>s1>>s2;
int x=s1.size(),y=s2.size();
for(int i=1;i<=x;i++){
for(int j=1;j<=y;j++){
if(s1[i-1]==s2[j-1])f[i][j]=f[i-1][j-1]+1;
else f[i][j]=max(f[i-1][j],f[i][j-1]);
}
}
cout<<f[x][y];
}
背包dp
1.01背包
有一个容量为p的背包,有n个物品,每个物品的重量为w[i],价值为v[i]。如何放置物品是背包。
#include<bits/stdc++.h>
using namespace std;
int n,p,w[1001],v[1001],f[100001];
int main(){
cin>>n>>p;
for(int i=1;i<=n;i++)cin>>w[i]>>v[i];
for(int i=1;i<=n;i++){
for(int j=p;j>=w[i];j--){
f[j]=max(f[j-w[i]]+v[i],f[j]);
}
}
cout<<f[p];
}
2.满背包
#include<bits/stdc++.h>
using namespace std;
int n,p,v[10001];
bool f[100001]={true};
int main(){
cin>>n>>p;
for(int i=1;i<=n;i++)cin>>v[i];
for(int i=1;i<=n;i++){
for(int j=p;j>=v[i];j--){
f[j]|=f[j-v[i]];
}
}
if(f[p])cout<<"true";
else cout<<"false";
}
3.多重背包
#include<bits/stdc++.h>
using namespace std;
int n,p,w[1001],v[1001],c[1001],f[100001];
int main(){
cin>>n>>p;
for(int i=1;i<=n;i++)cin>>c[i]>>w[i]>>v[i];
for(int i=1;i<=n;i++){
for(int j=p;j>=0;j--){
for(int k=1;k<=min(c[i],p/w[i]);k++){
if(j>=k*w[i])f[j]=max(f[j],f[j-k*w[i]]+k*v[i]);
}
}
}
cout<<f[p];
}
4.完全背包
#include<bits/stdc++.h>
using namespace std;
int n,p,w[1001],v[1001],f[100000];
int main(){
cin>>n>>p;
for(int i=1;i<=n;i++)cin>>w[i]>>v[i];
for(int i=1;i<=n;i++){
for(int j=w[i];j<=p;j++){
f[j]=max(f[j-w[i]]+v[i],f[j]);
}
}
cout<<f[p];
}