Always keep a beginner's mind, don't forge|

Tobaa

园龄:1年粉丝:1关注:1

dp总结(未完)

log:

点击查看日志

(25.1.12 开始编写)

.....

(25.1.22 加入完全和多重背包)

(25.1.23 更新例题)


动态规划

对于一个能用动态规划解决的问题,一般采用如下思路解决:
1.将原问题划分为若干 阶段,每个阶段对应若干个子问题,提取这些子问题的特征(称之为 状态);
2.寻找每一个状态的可能 决策,或者说是各状态间的相互转移方式(用数学的语言描述就是 状态转移方程)。
3.按顺序求解每一个阶段的问题。
4.能用dp解决的问题需要有三个要素:最优子结构,无后效性和子问题重叠。

1.背包问题

例1.1 01背包之:[NOIP2005 普及组] 采药

问题:总共有 M 个草药,每个草药得价值为 v ,每采一个草药需要花 t 个时间,要求要在一个固定时间 T 内使采得的药物总价值最大。

方法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;
}

结过:超时,时间复杂度为 O(2N) 级别。

方法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 普及组] 装箱问题

问题:有一个箱子容量为 V,同时有 n 个物品,每个物品有一个体积。现在从 n 个物品中,任取若干个装入箱内(也可以不取),使箱子的剩余空间最小。输出这个最小值。

方法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(动态规划)

解法:推动态转移方程,有两种情况,装和不装,不装为 dp[i][j]=dp[i1][j],装为 dp[i][j]=dp[i1][j]+w[i],代码懒得黏了。

1.3空间优化

见此处

1.4 二维01背包

二维01背包就是有两个性质。
题目:NASA的食物计划`
很轻松的推出状态转移方程,几乎和模板没区别,只是多了一维。
1.拿: dp[i][j][z]=dp[i1][j][z]
2.不拿:dp[i][j][z]=dp[i1][jh[i]][zt[i]]+k[i]减去损耗再加上卡路里
得到状态转移方程: dp[i][j][z]=max(dp[i1][j][z],dp[i1][jh[i]][zt[i]]+k[i])
代码:

	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];
			}	
		}
	}

当然还可已进行优化,把之前的滚动数组变成二维:
dp[j][z]=max(dp[j][z],dp[jh[i]][zt[i]]+k[i])

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;
}

状态转移方程
dpnum[j][k]=max(dpnum[j][k],dpnum[jrmb[i]][krp[i]]+1)
dptime[j][k]=min(dptime[j][k],dptime[jrmb[i]][krp[i]]+time[i])

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 多重背包

和完全背包不同的是,它拿物品的次数是有限制的,如只能拿 num 次。

解法和模板:

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 太空电梯

解法:

套多重背包的板子,把价值和重量都变成高度,背包的容积为限制高度,次数为材料可用次数,注意高度限制越低的越往下垒。可得动态转移方程为 dp[j]=max(dp[j],dp[jh[i]]+h[i]) 。题目整体难度不大,但要注意细节的把控。

代码:
#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] 纪念品

解法:

考虑明天剩余的钱,今天买了明天再买,得出动态转移方程 dp[ka[i][j]]=max(dp[ka[i][j]],dp[k]a[i][j]+a[i1][j]) 。dp[k]相当于还剩 k 钱的可获得的最大价值,太几把复杂了,当然还有第二种方法。

代码:
#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 中国大陆许可协议进行许可。

posted @   Tobaa  阅读(6)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起