dp总结(未完)
log:
点击查看日志
(25.1.12 开始编写)
.....
(25.1.22 加入完全和多重背包)
(25.1.23 更新例题)
动态规划
对于一个能用动态规划解决的问题,一般采用如下思路解决:
1.将原问题划分为若干 阶段,每个阶段对应若干个子问题,提取这些子问题的特征(称之为 状态);
2.寻找每一个状态的可能 决策,或者说是各状态间的相互转移方式(用数学的语言描述就是 状态转移方程)。
3.按顺序求解每一个阶段的问题。
4.能用dp
解决的问题需要有三个要素:最优子结构,无后效性和子问题重叠。
1.背包问题
例1.1 01背包之:[NOIP2005 普及组] 采药
问题:总共有
方法1:dfs
暴力搜索
解法:一个一个往前摸
完整代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int t[N],v[N],ans;
int T,M;
void dfs(int sumv,int pos,int sumt){
if(sumt>T) return ;
if(pos==M) {
ans=max(ans,sumv);
return ;
}
dfs(sumv+v[pos],pos+1,sumt+t[pos]);
dfs(sumv,pos+1,sumt);//回溯
}
int main(){
cin>>T>>M;
for(int i=0;i<M;i++){
cin>>t[i]>>v[i];
}
dfs(0,0,0);
cout<<ans;
return 0;
}
结过:超时,时间复杂度为
方法2:dp
(动态规划)
解法:
考虑这几种情况:
1.不摘草药
时间不变,把前面得状态复制过来,
dp[i][j]=dp[i-1][j];
2.采摘草药
当前时间减去该草药所需得时间,并且加上该草药的价值,
dp[i][j]=dp[i-1][j-t[i]]+v[i];
然后进行比较
dp[i][j]=max(dp[i-1][j],dp[i-1][j-t[i]]+v[i]);
完整代码:
#include <bits/stdc++.h>
using namespace std;
int t[1001],v[101],dp[1001][1001];
int main(){
int T,M;
cin>>T>>M;
for(int i=1;i<=M;i++){
cin>>t[i]>>v[i];
}
for(int i=1;i<=M;i++){
for(int j=1;j<=T;j++){
if(j>=t[i]){
dp[i][j]=max(dp[i-1][j-t[i]]+v[i],dp[i-1][j]);
}
else{
dp[i][j]=dp[i-1][j];
}
}
}
cout<<dp[M][T];
return 0;
}
结过:AC
例1.2 :[NOIP2001 普及组] 装箱问题
问题:有一个箱子容量为
方法1:dfs
无剪枝暴力
解法:题目就是求背包最多能装多少,只不过是换个问法而已,暴搜挨个试,
void dfs(int pos,int sumw){
if(sumw>v) return ;
if(pos>n){
ans=max(sumw,ans);
return ;
}
dfs(pos+1,sumw+w[pos]);
dfs(pos+1,sumw);
}
数据水所以能ac。
方法2:dp
(动态规划)
解法:推动态转移方程,有两种情况,装和不装,不装为
1.3空间优化
1.4 二维01背包
二维01背包就是有两个性质。
题目:NASA的食物计划`
很轻松的推出状态转移方程,几乎和模板没区别,只是多了一维。
1.拿:
2.不拿:
得到状态转移方程:
代码:
for(int i=1;i<=n;i++){
for(int j=1;j<=h;j++){
for(int z=1;z<=t;z++){
if(j>=hs[i]&&z>=ts[i]){
dp[i][j][z]=max(dp[i-1][j][z],dp[i-1][j-hs[i]][z-ts[i]]+k[i]);
}
else dp[i][j][z]=dp[i-1][j][z];
}
}
}
当然还可已进行优化,把之前的滚动数组变成二维:
for(int i=1;i<=n;i++){
for(int j=h;j>=hs[i];j++){
for(int z=t;z>=ts[i];z++){
dp[j][z]=max(dp[j][z],dp[j-h[i]][z-t[i]]+k[i])
}
}
}
1.5 变形题
例:P1509 找啊找啊找GF
多维dp+次要性动态规划,既要保证两个最优,可以设置两个dp来操作,一个人数一个时间,状态转移方程还是很好退的,就怕写的时候漏条件了。
无优化的代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e2+10;
int n,rmb[N],rp[N],t[N],dpn[N][N][N],dpt[N][N][N];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>rmb[i]>>rp[i]>>t[i];
}
int m,r;
cin>>m>>r;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=1;k<=r;k++){
if(rmb[i]<=j&&rp[i]<=k){
if(dpn[i-1][j][k]<dpn[i-1][j-rmb[i]][k-rp[i]]+1){
dpn[i][j][k]=dpn[i-1][j-rmb[i]][k-rp[i]]+1;
dpt[i][j][k]=dpt[i-1][j-rmb[i]][k-rp[i]]+t[i];
}
else {
if(dpn[i-1][j][k]==dpn[i-1][j-rmb[i]][k-rp[i]]+1){
dpn[i][j][k]=dpn[i-1][j][k];
dpt[i][j][k]=min(dpt[i-1][j][k],dpt[i-1][j-rmb[i]][k-rp[i]]+t[i]);
}
else {
dpn[i][j][k]=dpn[i-1][j][k];
dpt[i][j][k]=dpt[i-1][j][k];
}
}
}
else {
dpn[i][j][k]=dpn[i-1][j][k];
dpt[i][j][k]=dpt[i-1][j][k];
}
}
}
}
cout<<dpt[n][m][r];
return 0;
}
进行优化后的代码:
#include <bits/stdc++.h>
using namespace std;
int rmb[1001],rp[1001],t[1001],dpn[1001][1001],dpt[1001][1001];
int main(){
int n,m,r;
cin>>n;
for(int i=1;i<=n;i++) cin>>rmb[i]>>rp[i]>>t[i];
cin>>m>>r;
for(int i=1;i<=n;i++){
for(int j=m;j>=rmb[i];j--){
for(int k=r;k>=rp[i];k--){
if(dpn[j][k]<dpn[j-rmb[i]][k-rp[i]]+1){
dpn[j][k]=dpn[j-rmb[i]][k-rp[i]]+1;
dpt[j][k]=dpt[j-rmb[i]][k-rp[i]]+t[i];
}
else {
if(dpn[j][k]==dpn[k-rmb[i]][j-t[i]]+1) dpt[j][k]=min(dpt[j][k],dpt[j-rmb[i]][k-rp[i]]+t[i]);
}
}
}
}
cout<<dpt[m][r];
return 0;
}
状态转移方程
1.6 完全背包
不同于01背包,完全背包可以无限次拿取物品,开了。
解法:
01背包压维优化可知如果二层循环是正序的话,就会多次拿取物品,符合完全背包的要求。
模板
for(int i=1;i<=n;i++){
for(int j=w[i];j<=w;j++){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
1.7 多重背包
和完全背包不同的是,它拿物品的次数是有限制的,如只能拿
解法和模板:
1.像01背包一样当成单个物品使用。
for(int i=1;i<=n;i++){
for(int l=1;l<=num[i];l++){//使用num次
for(int j=w;j>=w[i],j--){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
}
for(int i=1;i<=mxx;i++){
mx=max(mx,dp[i]);
}
2.考虑使用情况,也就是使用次数。
for(int i=1;i<=n;i++){
for(int j=0;j<=w;j++){
for(int l=1;l<=num[i];l++){
if(l*w[i]<j){
dp[j]=max(dp[j],dp[j-l*w[i]]+l*v[i]);
}
}
}
}
for(int i=1;i<=mxx;i++){
mx=max(mx,dp[i]);
}
1.8 一些例题
1.P6771 [USACO05MAR] Space Elevator 太空电梯
解法:
套多重背包的板子,把价值和重量都变成高度,背包的容积为限制高度,次数为材料可用次数,注意高度限制越低的越往下垒。可得动态转移方程为
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=4e4+10;
int dp[N];
struct Build{
int h,a,c;
}b[N];
bool cmp(Build aa,Build b){
return aa.a<b.a;
}
int main(){
int n,mxx=0;
cin>>n;
for(int i=1;i<=n;i++){
cin>>b[i].h>>b[i].a>>b[i].c;
mxx=max(mxx,b[i].a);
}
sort(b+1,b+n+1,cmp);
for(int i=1;i<=n;i++){
for(int l=1;l<=b[i].c;l++){
for(int j=b[i].a;j>=b[i].h;j--){
dp[j]=max(dp[j],dp[j-b[i].h]+b[i].h);
}
}
}
int mx=INT_MIN;
for(int i=1;i<=mxx;i++){
mx=max(mx,dp[i]);
}
cout<<mx;
return 0;
}
2.P5662 [CSP-J2019] 纪念品
解法:
考虑明天剩余的钱,今天买了明天再买,得出动态转移方程
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int a[N][N];
int dp[N];
int main(){
int t,n,m;
cin>>t>>n>>m;
int tmp=1;
for(int i=1;i<=t;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
//int mx=0;
int ans=m;
for(int i=1;i<t;i++){
memset(dp,0,sizeof(dp));//初始化
dp[ans]=ans;//不买不买时候的价值
for(int j=1;j<=n;j++){
for(int k=ans;k>=a[i][j];k--){//明天的的钱数
dp[k-a[i][j]]=max(dp[k-a[i][j]],dp[k]+a[i+1][j]-a[i][j]);//如果买了,且明天的价值更大就直接卖了
}
}
int mx=0;
for(int j=0;j<=ans;j++){
mx=max(mx,dp[j]);
}
ans=mx;
}
cout<<ans<<endl;
return 0;
}
本文作者:Tobaa
本文链接:https://www.cnblogs.com/TobyL/p/18666861
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!