dp复习---之01背包(第一章)

本文是作者对动态规划中01背包问题的复习笔记,通过[LGOJ]P1048 采药和[LGOJ]P1802 5倍经验日两个例题,详细解释了01背包的状态转移方程,优化方法,并提供了相关代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

最近学习了许多新的,高级的算法,但是------

由于dp过菜,愤然决定开始dp复习qaq

开始之前,关于动态规划:
应用范围:动态规划方法一般用来求解最优化问题。这类问题可以有很多可行解,每个解都有一个值,我们希望找到具有最优值的解,我们称这样的解为问题的一个最优解,而不是最优解,因为可能有多个解都达到最优值。
解决动态规划问题一般分为四步:
1、定义一个状态,这是一个最优解的结构特征
2、进行状态递推,得到递推公式
3、进行初始化
4、返回结果

背包

01背包

首先是最基础的01背包,话不多说看题:
[LGOJ]P1048 采药,一个非常非常超级超级(懂的自然懂)裸的01背包;

题目描述
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。
如果你是辰辰,你能完成这个任务吗?

输入格式
第一行有 22 个整数 TT(1 \le T \le 10001≤T≤1000)和 MM(1 \le M \le 1001≤M≤100),用一个空格隔开,TT 代表总共能够用来采药的时间,MM 代表山洞里的草药的数目
接下来的 MM 行每行包括两个在 11 到 100100 之间(包括 11 和 100100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式
输出在规定的时间内可以采到的草药的最大总价值。

拿到题,第一反应是设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i件物品,花费 t t t的时间所能采到最高价值的药,于是我们不难得到

  • 当剩余时间无法采到该药时 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j]
  • 否则 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j − c o s t [ i ] ] + v a l [ i ] , d p [ i − 1 ] [ j ] ) dp[i][j]=max(dp[i-1][j-cost[i]]+val[i],dp[i-1][j]) dp[i][j]=max(dp[i1][jcost[i]]+val[i],dp[i1][j])

于是上代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
int t,m;
int dp[maxn][maxn],cost[maxn],val[maxn];
inline int read()
{
	int s=0,f=1;
	char c=getchar();
	while (c<'0'||c>'9')
	{
		if (c=='-')
		{
			f=-1;
		}
		c=getchar();
	}
	while (c>='0'&&c<='9')
	{
		s=s*10+c-48;
		c=getchar();
	}
	return s*f;
}
int main()
{
	t=read(),m=read();
	for (int i=1;i<=m;i++)
	{
		cost[i]=read(),val[i]=read();
	}
	for (int i=1;i<=m;i++)
	{
		for (int j=t;j>=0;j--)
		{
			if (cost[i]>j)
			{
				dp[i][j]=dp[i-1][j];
			}
			else
			{
				dp[i][j]=max(dp[i-1][j-cost[i]]+val[i],dp[i-1][j]);
			}
		}
	}
	cout<<dp[m][t]<<endl;
    return 0;
}

嗯,这种方法看起来还不错,但是空间达到了 O ( N ∗ M ) O(N*M) O(NM)!!!,我们又发现 d p [ i ] [ j ] dp[i][j] dp[i][j]这一状态全部都是来自于 i − 1 i-1 i1,于是不难想到,我们可以把第一维给优化掉得到:
d p [ j ] = m a x ( d p [ j ] , d p [ j − c o s t [ i ] ] + v a l [ i ] ) ; dp[j]=max(dp[j],dp[j-cost[i]]+val[i]); dp[j]=max(dp[j],dp[jcost[i]]+val[i]);

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
int t,m;
int dp[maxn],cost[maxn],val[maxn];
inline int read()
{
	int s=0,f=1;
	char c=getchar();
	while (c<'0'||c>'9')
	{
		if (c=='-')
		{
			f=-1;
		}
		c=getchar();
	}
	while (c>='0'&&c<='9')
	{
		s=s*10+c-48;
		c=getchar();
	}
	return s*f;
}
int main()
{
	t=read(),m=read();
	for (int i=1;i<=m;i++)
	{
		cost[i]=read(),val[i]=read();
	}
	for (int i=1;i<=m;i++)
	{
		for (int j=t;j>=cost[i];j--)
		{
			dp[j]=max(dp[j],dp[j-cost[i]]+val[i]);
		}
	}
	cout<<dp[t]<<endl;
    return 0;
}

例题2:[LGOJ]P1802 5倍经验日

题面:
题目描述
现在absi2011拿出了x个迷你装药物(嗑药打人可耻….),准备开始与那些人打
由于迷你装一个只能管一次,所以absi2011要谨慎的使用这些药,悲剧的是,没到达最少打败该人所用的属性药了他打人必输>.<所以他用2个药去打别人,别人却表明3个药才能打过,那么相当于你输了并且这两个属性药浪费了
现在有n个好友,有输掉拿的经验、赢了拿的经验、要嗑几个药才能打过。求出最大经验(注意,最后要乘以5

输入格式
第一行两个数,n和
后面n行每行三个数,分别表示输了拿到的经验( l o s e [ i ] lose[i] lose[i])、赢了拿到的经验( w i n [ i ] win[i] win[i])、打过要至少使用的药数量( u s e [ i ] use[i] use[i])

输出格式
一个整数,最多获得的经验

这题稍微有些变形,但是难度比较小。
看完题自然而然想到 d p [ i ] dp[i] dp[i]表示 i i i瓶药能拿到的最大经验,于是状态转移方程便很显而易见了:
当剩余的药还能打这一个朋友时 d p [ j ] = m a x ( d p [ j ] + l o s e [ i ] , d p [ j − u s e [ i ] ] + w i n [ i ] ) ; dp[j]=max(dp[j]+lose[i],dp[j-use[i]]+win[i]); dp[j]=max(dp[j]+lose[i],dp[juse[i]]+win[i]);
否则 d p [ j ] + = l o s e [ i ] ; dp[j]+=lose[i]; dp[j]+=lose[i];

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1000005;
int n,x;
int dp[maxn],lose[maxn],win[maxn],use[maxn];
inline int read()
{
	int s=0,f=1;
	char c=getchar();
	while (c<'0'||c>'9')
	{
		if (c=='-')
		{
			f=-1;
		}
		c=getchar();
	}
	while (c>='0'&&c<='9')
	{
		s=s*10+c-48;
		c=getchar();
	}
	return s*f;
}
signed main()
{
	n=read(),x=read();
	for (int i=1;i<=n;i++)
	{
		lose[i]=read(),win[i]=read(),use[i]=read();
	}
	for (int i=1;i<=n;i++)
	{
		for (int j=x;j>=0;j--)
		{
			if (j>=use[i])
			{
				dp[j]=max(dp[j]+lose[i],dp[j-use[i]]+win[i]);
			}
			else
			{
				dp[j]+=lose[i];
			}
		}
	}
	cout<<5*dp[x]<<endl;
    return 0;
}

ok,最基础的01背包就到这里了,接下来我们要迈进下一步了~~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值