01背包8.21

P1510 精卫填海 题解复盘

模块:01背包

类型:至少装满 + 最小代价

目标:总体积至少达到 V,在消耗体力不超过 C 的前提下,使剩余体力最大。


基本信息

项目 内容
题目编号、来源 P1510 洛谷
训练层级 普及−
知识版块 01背包、最小代价背包、至少装满

解题前・关键信号识别

维度 分析
目标、约束、底层结构 每块石头只能使用一次,总体积至少达到 V,要求消耗体力最小。
数据规模 n 较大,但总体积不大,可以使用 01 背包。
候选算法和依据 每块石头只能选一次,因此是 01 背包;要求体积至少达到 V,并使花费最小,因此属于最小代价背包。
复杂度预判 时间复杂度:O(n×limit) 空间复杂度:O(limit)

解题后・外化复盘

维度 内容
状态定义 dp[j] 表示:总体积恰好为 j 时,需要消耗的最小体力。
状态转移 dp[j]=min(dp[j],dp[j-vol[i]]+cost[i])
遍历顺序 每块石头只能选一次,因此体积必须倒序枚举。
实现结构 / 核心思路 ① 初始化所有状态为 INF。 ② dp[0]=0。 ③ 枚举每块石头进行01背包。 ④ 在所有 j≥V 的状态中寻找最小体力。 ⑤ 判断是否能够完成填海。
错因回溯 ① 一开始把它当成普通最大价值背包。 ② 忘记初始化 dp[0]=0。 ③ 数组 volcost 开成 105 导致 WA,应开到 10005。 ④ 忘记判断 best==INF
边界和易错点 1. dp 初始化为 INF。 2. dp[0]=0。 3. 背包容量必须倒序。 4. 最后答案是在所有 j≥V 中找最小值,而不是只看 dp[V]。 5. 若 best==INFbest>C,输出 Impossible。
下次看到什么信号,我应该想到这个方法 看到: ① 每个物品只能选一次。 ② 至少达到某个容量。 ③ 求最小代价。 想到: 01背包(至少装满)+ 最小代价DP。

核心思想

第一步:状态定义

定义:

cpp 复制代码
dp[j]

表示:

总体积恰好为 j 时,需要消耗的最小体力。

例如:

text 复制代码
dp[120]=15

表示:

总体积恰好为 120,

最少需要消耗 15 点体力。


第二步:为什么初始化为 INF?

开始时:

除了体积为 0,

其它体积都无法达到。

因此:

cpp 复制代码
for(int i=0;i<=limit;i++)
    dp[i]=INF;

表示:

text 复制代码
目前不能达到这些体积。

第三步:为什么 dp0=0?

什么石头都不拿:

text 复制代码
总体积=0

消耗体力:

text 复制代码
0

因此:

cpp 复制代码
dp[0]=0;

这是整个 DP 的起点。

以后所有状态,

都是由:

text 复制代码
dp[0]

一步一步转移出来的。


第四步:状态转移

当前石头:

text 复制代码
体积=vol

体力=cost

如果以前:

text 复制代码
总体积=j-vol

能够达到,

那么:

加入当前石头以后:

text 复制代码
总体积=j

体力变成:

cpp 复制代码
dp[j-vol]+cost

因此:

cpp 复制代码
dp[j]=min(dp[j],dp[j-vol]+cost);

第五步:为什么倒序?

因为:

每块石头:

text 复制代码
只能使用一次

因此:

cpp 复制代码
for(int j=limit;j>=vol[i];j--)

如果正序:

text 复制代码
当前石头

刚更新出的状态

又继续更新

↓

相当于

同一块石头用了两次

因此:

必须倒序。


第六步:为什么最后不是 dpV

题目要求:

text 复制代码
总体积

至少

V

不是:

text 复制代码
恰好

V

例如:

text 复制代码
V=100

可能:

text 复制代码
95

×

但是:

text 复制代码
110

√

因此:

最后:

cpp 复制代码
int best=INF;

for(int j=V;j<=limit;j++){
    best=min(best,dp[j]);
}

寻找:

text 复制代码
所有

≥V

中的最小体力

第七步:为什么输出 C-best?

题目要求:

text 复制代码
剩余体力最大

如果:

初始体力:

text 复制代码
C

最少消耗:

text 复制代码
best

那么:

剩余:

cpp 复制代码
C-best

即可。


AC完整代码

cpp 复制代码
#include<iostream>
#include<algorithm>
using namespace std;

const int INF=1e9;

int main(){

    int V,n,C;
    cin>>V>>n>>C;

    int vol[10005],cost[10005];

    int maxv=0;

    for(int i=1;i<=n;i++){
        cin>>vol[i]>>cost[i];
        maxv=max(maxv,vol[i]);
    }

    int limit=V+maxv;

    int dp[20005];

    for(int i=0;i<=limit;i++){
        dp[i]=INF;
    }

    dp[0]=0;

    for(int i=1;i<=n;i++){

        for(int j=limit;j>=vol[i];j--){

            dp[j]=min(
                dp[j],
                dp[j-vol[i]]+cost[i]
            );
        }
    }

    int best=INF;

    for(int j=V;j<=limit;j++){
        best=min(best,dp[j]);
    }

    if(best==INF || best>C)
        cout<<"Impossible";
    else
        cout<<C-best;

    return 0;
}

模型总结

普通01背包 本题
求最大价值 求最小代价
容量不能超过 V 容量至少达到 V
最后看 dpV 最后枚举所有 ≥V 的状态
max() min()

记忆

text 复制代码
看到:

① 每个物品只能选一次

↓

01背包


② 至少达到某个容量

↓

最后枚举:

j≥V


③ 求最小花费

↓

dp初始化INF

↓

dp[0]=0

↓

状态转移:

dp[j]=min(dp[j],dp[j-vol]+cost)


最后:

best=min(dp[j])

↓

答案:

C-best

P1910 L 国的战斗之间谍 题解复盘

模块:动态规划

类型:二维费用01背包

目标:在伪装能力和工资都不超过限制的情况下,使获得资料最多。


基本信息

项目 内容
题目编号、来源 P1910 洛谷原创
训练层级 普及−
知识版块 二维费用01背包、二维DP

解题前・关键信号识别

维度 分析
目标、约束、底层结构 每个人只能选一次,同时受到伪装能力和工资两个限制,要求资料最大。
数据规模 两个容量都较小,可以二维DP。
候选算法和依据 每个人只能选择一次,因此是01背包;存在两个限制,因此是二维费用背包。
复杂度预判 时间复杂度:O(n×m×x) 空间复杂度:O(m×x)

解题后・外化复盘

维度 内容
状态定义 dp[j][k] 表示:伪装能力不超过 j,工资不超过 k 时,能够获得的最大资料。
状态转移 dp[j][k]=max(dp[j][k],dp[j-b][k-c]+a)
遍历顺序 两个容量都必须倒序,因为每个人只能选择一次。
实现结构 / 核心思路 ① 枚举每个人。 ② 第一维容量倒序。 ③ 第二维容量倒序。 ④ 更新最大资料。
错因回溯 ① 容易忘记两个维度都要倒序。 ② 容易把二维费用背包写成两个一维DP。
边界和易错点 1. 两个容量都必须倒序。 2. 每个人只能选一次。 3. dp 默认初始化为0即可。
下次看到什么信号,我应该想到这个方法 看到: ① 每个物品只能选一次。 ② 有两个容量限制。 ③ 求最大价值。 想到: 二维费用01背包。

核心思想

第一步:状态定义

定义:

cpp 复制代码
dp[j][k]

表示:

伪装能力不超过 j,工资不超过 k 时,能够获得的最大资料。

例如:

text 复制代码
dp[5][20]=30

表示:

text 复制代码
总伪装能力≤5

总工资≤20

最多获得30份资料

第二步:状态转移

当前间谍:

text 复制代码
资料=a

伪装能力=b

工资=c

如果选择当前间谍:

那么之前必须满足:

text 复制代码
伪装能力:

j-b

工资:

k-c

因此:

cpp 复制代码
dp[j][k]=max(dp[j][k],dp[j-b][k-c]+a);

第三步:为什么两个维度都要倒序?

因为:

每个人:

text 复制代码
只能选一次

如果:

第一维正序:

当前人可能被重复使用。

如果:

第二维正序:

当前人同样可能被重复使用。

因此:

必须:

cpp 复制代码
for(int j=m;j>=b;j--)

以及:

cpp 复制代码
for(int k=x;k>=c;k--)

两个容量都倒序。


第四步:最后为什么输出 dpmx

因为:

text 复制代码
dp[j][k]

表示:

text 复制代码
容量不超过

j、k

因此:

cpp 复制代码
dp[m][x]

已经包含了:

text 复制代码
所有满足限制的方案

直接输出即可。


AC完整代码

cpp 复制代码
#include<iostream>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;

struct node{
    int a,b,c;
};

int main(){

    int n,m,x;
    cin>>n>>m>>x;

    vector<node> v(n+1);

    for(int i=1;i<=n;i++){
        cin>>v[i].a>>v[i].b>>v[i].c;
    }

    int dp[1005][1005]={0};

    for(int i=1;i<=n;i++){

        for(int j=m;j>=v[i].b;j--){

            for(int k=x;k>=v[i].c;k--){

                dp[j][k]=max(
                    dp[j][k],
                    dp[j-v[i].b][k-v[i].c]+v[i].a
                );
            }
        }
    }

    cout<<dp[m][x];

    return 0;
}

模型总结

普通01背包 本题
一个容量 两个容量
dp[j] dp[j][k]
一层倒序 两层都倒序
最大价值 最大资料

记忆

text 复制代码
看到:

① 每个物品只能选一次

↓

01背包


② 有两个限制

例如:

重量

体积

时间

金钱

↓

二维费用背包


状态:

dp[j][k]


转移:

dp[j][k]

=

max(

dp[j][k],

dp[j-b][k-c]+a

)


注意:

两个容量

都要倒序
相关推荐
ZJU_统一阿萨姆1 小时前
【算子开发】Reduction算子完全指南
人工智能·算法·语言模型
鹿角片ljp10 小时前
LeetCode 46. 全排列|吃透回溯
算法·leetcode·职场和发展
鼎艺创新科技10 小时前
不依赖 UE/Unity:我们如何从零搭建一套国产三维 GIS 渲染引擎
人工智能·算法·unity·游戏引擎·三维电子沙盘
石头猫灯11 小时前
WordPress wp2shell 漏洞链完整拆解流程
网络·数据结构·安全·web安全
.道阻且长.13 小时前
11.LeetCode算法习题讲解--滑动窗口--将x减到0的最小操作数
算法·leetcode·职场和发展
wenyq713 小时前
LeetCode 2460. Apply Operations to an Array
算法·leetcode
.格子衫.14 小时前
032动态规划之区间DP——算法备赛
算法·动态规划
小欣加油15 小时前
leetcode3069 将元素分配到两个数组中I
数据结构·c++·算法·leetcode·职场和发展
不会代码的小猴15 小时前
7. JSON
开发语言·c++·笔记·qt·算法·json