【算法题】【动态规划2】【背包动态规划】

背包动态规划问题解析

背包问题是动态规划中的经典问题系列,本文将详细解析九道背包类动态规划题目的解题思路和实现方法。

1. 基础背包问题

1.1 P1048 [NOIP 2005 普及组] 采药

问题本质:01背包问题

关键思路

  • 每株药草只能采一次(01背包)
  • dp[j] 表示在时间j内能获得的最大价值
  • 状态转移:dp[j] = max(dp[j], dp[j-w[i]] + val[i])
  • 需要逆序遍历容量,确保每个物品只使用一次
cpp 复制代码
for(int i = 1; i <= m; i++) {
    for(int j = t; j >= w[i]; j--) {  // 逆序遍历
        dp[j] = max(dp[j - w[i]] + val[i], dp[j]);
    }
}

1.2 P1060 [NOIP 2006 普及组] 开心的金明

问题特点:01背包的变体

注意事项

  • 价值是价格×重要度,不是直接输入
  • 状态转移与标准01背包相同
  • 只需要处理 j >= v[i] 的情况

2. 多维背包问题

2.1 P1855 榨取kkksc03

问题类型:二维背包(双重限制)

解题要点

  • 同时受到金钱M和时间T的限制
  • dp[j][k] 表示使用j金钱和k时间能处理的最大请求数
  • 状态转移需要同时满足两个维度的限制
cpp 复制代码
for(int i = 1; i <= n; i++) {
    for(int j = M; j >= m[i]; j--) {
        for(int k = T; k >= t[i]; k--) {
            dp[j][k] = max(dp[j][k], dp[j-m[i]][k-t[i]] + 1);
        }
    }
}

3. 完全背包与货币系统

3.1 P5020 [NOIP 2018 提高组] 货币系统

核心思想:完全背包 + 最小表示集

解题步骤

  1. 对货币面额排序
  2. 使用完全背包判断每个面额是否能被更小的面额组合表示
  3. 不能被表示的面额必须保留在简化系统中

关键代码

cpp 复制代码
sort(a + 1, a + 1 + n);  // 排序面额
f[0] = 1;  // 0元可以被表示

for(int i = 1; i <= n; i++) {
    if(f[a[i]]) {  // 如果当前面额能被表示
        ans--;     // 不需要保留
        continue;
    }
    // 完全背包更新
    for(int j = a[i]; j <= a[n]; j++) {
        f[j] = f[j] | f[j - a[i]];
    }
}

4. 分组背包

4.1 P1757 通天之分组背包

问题特点:每组物品只能选一个

解题策略

  1. 将物品按组分类
  2. 三层循环:组数 → 容量(逆序) → 组内物品
  3. 确保在每个容量下,每组只选择一个物品
cpp 复制代码
for(int i = 1; i <= t; i++) {           // 遍历组
    for(int j = v; j >= 0; j--) {       // 逆序遍历容量
        for(int k = 1; k <= b[i]; k++) { // 遍历组内物品
            if(j >= w[g[i][k]])
                dp[j] = max(dp[j], dp[j - w[g[i][k]]] + z[g[i][k]]);
        }
    }
}

5. 依赖背包(有附属品的背包)

5.1 P1064 [NOIP 2006 提高组] 金明的预算方案

问题结构:主件 + 附件(最多2个)

处理方式

  • 只有主件
  • 主件 + 附件1
  • 主件 + 附件2
  • 主件 + 附件1 + 附件2
    四种情况分别考虑

实现技巧

  • 使用数组记录每个主件的附件
  • 针对每种组合分别进行状态转移

6. 特殊背包问题

6.1 P2946 [USACO09MAR] Cow Frisbee Team S

问题特点:模数背包

核心思路

  • f[i][j] 表示前i头牛,能力之和模F等于j的方案数
  • 状态转移:f[i][j] = f[i-1][j] + f[i-1][(j-cow[i]+F)%F]
  • 注意取模操作

6.2 P1156 垃圾陷阱

问题类型:时间与高度双重维度的动态规划

解题关键

  • f[j] 表示高度为j时的最大存活时间
  • 两种选择:堆放(增加高度)或吃掉(增加时间)
  • 需要按时间排序垃圾
cpp 复制代码
for(int i = 1; i <= g; i++) {
    for(int j = d; j >= 0; j--) {
        if(f[j] >= c[i].t) {  // 当前时间足够处理这个垃圾
            if(j + c[i].h >= d) {  // 可以逃脱
                cout << c[i].t;
                return 0;
            }
            // 堆放垃圾
            f[j + c[i].h] = max(f[j + c[i].h], f[j]);
            // 吃掉垃圾
            f[j] += c[i].l;
        }
    }
}

6.3 P5322 [BJOI2019] 排兵布阵

问题特点:分组背包的变形

处理步骤

  1. 按城堡分组,每个城堡是一个"组"
  2. 对于每个城堡,按对手的兵力排序
  3. 用分组背包求解最优分配

背包问题总结

问题类型 特点 遍历顺序 时间复杂度
01背包 每个物品选一次 容量逆序 O(n×capacity)
完全背包 物品无限次 容量正序 O(n×capacity)
分组背包 每组选一个 组→容量逆序→组内物品 O(n×capacity)
多维背包 多重限制 各维度都逆序 O(n×∏capacity_i)
依赖背包 物品间有依赖 按组合情况处理 组合数×O(n×capacity)

解题技巧

  1. 明确背包类型:01背包、完全背包还是其他变体
  2. 确定状态定义:dp数组的含义要清晰
  3. 注意遍历顺序
    • 01背包:容量逆序
    • 完全背包:容量正序
    • 分组背包:先组,再逆序容量,最后组内物品
  4. 边界条件 :通常 dp[0] = 0 或根据题目设定
  5. 空间优化:大多数背包问题可以优化为一维数组

通过这九道题目的练习,可以掌握背包动态规划的核心思想和常见变体的解决方法。

背包动态规划题

1 P1048 [NOIP 2005 普及组] 采药

https://www.luogu.com.cn/problem/P1048

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int w[105],val[105];
int dp[1005];
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t,m,res=-1;
	cin>>t>>m;
	for(int i=1;i<=m;i++){
	   cin>>w[i]>>val[i];
	}for(int i=1;i<=m;i++){
		for(int j=t;j>=0;j--){
			if(j>=w[i]){
				dp[j]=max(dp[j-w[i]]+val[i],dp[j]);
			}
		}
	}cout<<dp[t];
}

2 P1060 [NOIP 2006 普及组] 开心的金明

https://www.luogu.com.cn/problem/P1060

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int w[30],v[30],f[50000];
int n,m;
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>m>>n;
	for(int i=1;i<=n;i++){
		cin>>v[i]>>w[i];
		w[i]*=v[i];
	}for(int i=1;i<=n;i++){
		for(int j=m;j>=v[i];j--){
			if(j>v[i]){
				f[j]=max(f[j],f[j-v[i]]+w[i]);
			}
		}
	}cout<<f[m]<<'\n';
}

3 P1855 榨取kkksc03

https://www.luogu.com.cn/problem/P1855

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int n,M,T,dp[1010][1010];
int m[1010],t[1010];
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>M>>T;
	for(int i=1;i<=n;i++){
		cin>>m[i]>>t[i];
		for(int j=M;j>=m[i];j--){
			for(int k=T;k>=t[i];k--){
				dp[j][k]=max(dp[j][k],dp[j-m[i]][k-t[i]]+1);
			}
		}
	}cout<<dp[M][T];
}

4 P5020 [NOIP 2018 提高组] 货币系统

https://www.luogu.com.cn/problem/P5020

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int f[25005];
int a[105];

int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int i,j,n,T,ans;
	cin>>T;
	while(T--){
		memset(f,0,sizeof f);
		cin>>n;//当前组的面额为n
		ans=n;
		for(int i=1;i<=n;i++){
			cin>>a[i];
		}sort(a+1,a+1+n);
		f[0]=1;//0可以被表示
		for(int i=1;i<=n;i++){
			//能被别的面额表示
			if(f[a[i]]){
				ans--;continue;
			}
			//要么j本身能表示,j-a[i]能被表示
			for(int j=a[i];j<=a[n];j++){
				f[j]=f[j]|f[j-a[i]];
			}
		}cout<<ans<<'\n';
	}
}

5 P1757 通天之分组背包

https://www.luogu.com.cn/problem/P1757

每组只能选一个

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int v,n,t;
int x;
int g[205][205];
int i,j,k;
int w[10001],z[10001];
int b[10001];
int dp[10001];
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>v>>n;
	for(int i=1;i<=n;i++){
		cin>>w[i]>>z[i]>>x;
		t=max(t,x);//小组组数
		b[x]++;//小组的物品加1
		g[x][b[x]]=i;//g[i][j]表示i中的j
	}for(int i=1;i<=t;i++){//小组组数
		for(int j=v;j>=0;j--){//容量
			for(int k=1;k<=b[i];k++){//小组中的物品
				if(j>=w[g[i][k]])
					dp[j]=max(dp[j],dp[j-w[g[i][k]]]+z[g[i][k]]);
			}//遍历顺序:遵循「组 → 逆序容量 → 组内物品」(分组背包的标准顺序)
			//在某个容量中只会选一个这组的
		}
	}cout<<dp[v];
	
}

6 P1064 [NOIP 2006 提高组] 金明的预算方案

https://www.luogu.com.cn/problem/P1064

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
ll n,m,v[70],w[70],c[70],dp[32010],son[70][3],q[70];
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>v[i]>>w[i]>>q[i];
		c[i]=v[i]*w[i];
		if(q[i])son[q[i]][++son[q[i]][0]]=i;
	}for(int i=1;i<=m;i++){
		for(int j=n;j>=0;j--){
			if(q[i])continue;
			if(j>=v[i])
				dp[j]=max(dp[j],dp[j-v[i]]+c[i]);
			if(son[i][1]&&j>=v[i]+v[son[i][1]])
				dp[j]=max(dp[j],dp[j-v[i]-v[son[i][1]]]+c[i]+c[son[i][1]]);
			if(son[i][2]&&j>=v[i]+v[son[i][2]])
				dp[j]=max(dp[j],dp[j-v[i]-v[son[i][2]]]+c[i]+c[son[i][2]]);
			if(son[i][0]>=2&&j>=v[i]+v[son[i][1]]+v[son[i][2]])
				dp[j]=max(dp[j],dp[j-v[i]-v[son[i][1]]-v[son[i][2]]]+c[i]+c[son[i][1]]+c[son[i][2]]);
			
		}
	}cout<<dp[n];
}

7 P2946 [USACO09MAR] Cow Frisbee Team S

https://www.luogu.com.cn/problem/P2946

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int mod=1e8;
ll cow[2005],f[2005][2005];

int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n,F;
	cin>>n>>F;
	for(int i=1;i<=n;i++){
		cin>>cow[i];
		cow[i]%=F;//提前取模
	}for(int i=1;i<=n;i++){
		f[i][cow[i]]=1;//选一次是1
	}for(int i=1;i<=n;i++){
		for(int j=0;j<=F-1;j++){
			f[i][j]=((f[i][j]+f[i-1][j])%mod+f[i-1][(j-cow[i]+F)%F])%mod;
			//选与不选
		}
	}cout<<f[n][0];
}

8 P1156 垃圾陷阱

https://www.luogu.com.cn/problem/P1156

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
struct p{
	int t,h,l;
}c[101];
int d,g;
int ti[101];
int f[101];
bool cmp(p a,p b){
	return a.t<b.t;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>d>>g;//d:高度,g:数量
	for(int i=1;i<=g;i++){
		cin>>c[i].t>>c[i].l>>c[i].h;
	}sort(c+1,c+1+g,cmp);
	f[0]=10;
	for(int i=1;i<=g;i++){//遍历每个垃圾(时间处理)
		for(int j=d;j>=0;j--){
			if(f[j]>=c[i].t){//当前高度的时间>垃圾扔下的时间
				if(j+c[i].h>=d){//可跳出
					cout<<c[i].t;
					return 0;
				}f[j+c[i].h]=max(f[j+c[i].h],f[j]);//堆放后的时间等于原来的
				f[j]+=c[i].l;//吃掉当前垃圾的存活时间
			}
		}
	}cout<<f[0];
}

9 P5322 [BJOI2019] 排兵布阵

https://www.luogu.com.cn/problem/P5322

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int s,n,m,dp[20002],a[110][110],ans;

int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>s>>n>>m;//s名队友,n座城堡,m名士兵
	for(int i=1;i<=s;i++){
		for(int j=1;j<=n;j++){
			cin>>a[j][i];//j座城堡的第几名士兵
		}
	}for(int i=1;i<=n;i++){
		sort(a[i]+1,a[i]+1+s);
	}for(int i=1;i<=n;i++){
		for(int j=m;j>=0;j--){
			for(int k=1;k<=s;k++){
				if(j>a[i][k]*2)
					dp[j]=max(dp[j-a[i][k]*2-1]+k*i,dp[j]);//分组背包
			}
		}
	}for(int i=0;i<=m;i++){
	ans=max(ans,dp[i]);	
	}cout<<ans;
}

10

相关推荐
数研小生2 小时前
1688商品列表API:高效触达批发电商海量商品数据的技术方案
大数据·python·算法·信息可视化·json
2301_765703142 小时前
C++中的代理模式变体
开发语言·c++·算法
酉鬼女又兒2 小时前
27. 移除元素
数据结构·算法·排序算法
TracyCoder1232 小时前
LeetCode Hot100(28/100)——104. 二叉树的最大深度
算法·leetcode
执着2592 小时前
力扣hot100 - 101、对称二叉树
数据结构·算法·leetcode
多恩Stone2 小时前
【3D-AICG 系列-1】Trellis v1 和 Trellis v2 的区别和改进
人工智能·pytorch·python·算法·3d·aigc
mit6.8242 小时前
模运算|z函数 字符串匹配
算法
阿豪只会阿巴2 小时前
【吃饭香系列】二周目|代码随想录算法训练营第七天|454.四数相加II |383. 赎金信 |15. 三数之和 |18. 四数之和
算法
小O的算法实验室2 小时前
2025年COR SCI2区,考虑风场影响的无人机搜救覆盖路径规划精确界算法,深度解析+性能实测
算法·无人机·论文复现·智能算法·智能算法改进