P1048 [NOIP2005 普及组] 采药 题解

题目描述

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

如果你是辰辰,你能完成这个任务吗?

输入格式

第一行有 2 个整数 T(1≤T≤1000)和 M(1≤M≤100),用一个空格隔开,T 代表总共能够用来采药的时间,M 代表山洞里的草药的数目。

接下来的 M 行每行包括两个在 1 到 100 之间(包括 1 和 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式

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

输入输出样例

输入 #1

70 3
71 100
69 1
1 2

输出 #1

3

说明/提示

【数据范围】

  • 对于 30% 的数据,M≤10;
  • 对于全部的数据,M≤100。

【题目来源】

NOIP 2005 普及组第三题

一、做题方法

        这道题一看就是一道01背包的问题。我们可以先看看为什么要用01背包。(如果知道的话可以跳过)

        1.枚举法

                我们用0和1来表示一株草药采或不采(0为不采,1为采)。我们枚举所有的串,对每个串,我们看看它的总时间是不是小于等于T,再从所有总时间小于等于T的方案中,选出总价值最大的就可以啦。

                But,太慢了!

           \frac{1}{2}.贪心?

                可能有些人在刚刚看到这道题的时候第一反应觉得这是道贪心题,但是细一看就能找到反例。比如,采药时间是100,有三株草药,时间分别是51,50,50,价值分别是55,50,50。用贪心思想,第1株草药性价比高,先采第1株。可是一旦选了第1株草药,时间就只剩下了45,不够采第2、3株草药了。如果不采第1株,选看起来不如第1株草药的第2、3株,总时间正好100,这时总价值100,比55多多了。贪心策略无效。

        2.动态规划

                普通写法

                如果我们现在有了两种采法,总时间一样,那是不是我们只要价值更大的那组?

                那现在我们考虑采前 i 株草药采或不采,如果有两种采法总时间一样,我们就保留价值更大的那组。

                把考虑了前 i 株草药以后时间为0,1,2……,m时的最大收益都记下来。

                现在,考虑了前 i 株草药,总时间为 j 时,有两种情况:

                        第 i 株草药没采,问题变成考虑了前 i 株草药,总时间为 j 时的情况。

                        第 i 株草药采了,问题变成考虑了前 i 株草药,总时间为 j-w[i] 时的情况。

                思路有了,我们看看它满不满足动态规划的要求:

                最优子结构:要计算考虑了前 i 株草药,总时间为 j 时的最大收益,我们可以先计算考虑了前 i-1 株草药,总时间为 j 时的最大收益与考虑了前 i-1 株草药,总时间为 j-w[i] 时的最大收益。

                无后效性:我们只关注考虑了前 i 株草药,总时间为 j 时的最大收益,不关注哪些草药采了,哪些草药没采。

                状态:f[i][j] 表示考虑了前 i 株草药,总时间为 j 时的最大收益。

                转移:f[i][j] = max( f[i-1][j] , f[i-1][j-w[i]] + v[i] ) ,分别对应了第 i 株草药没采和采了的两种情况。

                没问题啊,那么我们就可以列出状态转移方程了。

                f(i,j)=\left\{\begin{matrix} f(i-1,j),j<w_{i}\\ max[f(i-1,j),f(i-1,j-w_{i})+v_{i}],j\geqslant w_{i}\end{matrix}\right.

                判断 j 是否小于 wi 是因为如果时间不够了,就不能采这株草药。

                代码:

#include<bits/stdc++.h>
using namespace std;
int v[1010],w[1010];
int f[1010][1010];
int main()
{
	int t,m;
	cin>>t>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>w[i]>>v[i];
	}
	for(int i=1;i<=m;i++)
	{
		for(int j=0;j<=t;j++)
		{
			if(j<w[i])
			{
				f[i][j]=f[i-1][j];
			}
			else
			{
				f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
			}
		}
	}
	cout<<f[m][t];
	return 0;
}

                空间优化

                第一种方法

                我们再考虑一下,到底需不需要开这么大的数组?

                其实,考虑了前 i 株草药时的状态,只和考虑了前 i-1 株草药时的状态相关,前面的 i-2 行完全不用记的!

                那要怎么办呢?

                我们可以用 f 数组和 g 数组分别记录第 i-1 株和第 i 株的情况,通过第 i-1 株的情况,去计算第 i 株的情况,最后把 g 数组赋值给 f 数组。

                代码:

#include<bits/stdc++.h>
using namespace std;
int v[1010],w[1010];
int f[1010],g[1010];
int main()
{
	int t,m;
	cin>>t>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>w[i]>>v[i];
	}
	for(int i=1;i<=m;i++)
	{
		for(int j=0;j<=t;j++)
		{
			if(j<w[i])
			{
				g[j]=f[j];
			}
			else
			{
				g[j]=max(f[j],f[j-w[i]]+v[i]);
			}
		}
		memcpy(f,g,sizeof(g));
	}
	cout<<f[t];
	return 0;
}
                第二种方法

                如果我们继续优化,把两个一维数组改成一个一维数组,可以吗?我们直接用 f[j] 来表示情况,也就是把 g[j] 改成 f[j] 。

               我们来试一下:

#include<bits/stdc++.h>
using namespace std;
int v[1010],w[1010];
int f[1010];
int main()
{
	int t,m;
	cin>>t>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>w[i]>>v[i];
	}
	for(int i=1;i<=m;i++)
	{
		for(int j=0;j<=t;j++)
		{
			f[j]=max(f[j],f[j-w[i]]+v[i]);
		}
	}
	cout<<f[t];
	return 0;
}

                输出240。???

                为什么呢?不知道大家注意没注意第2层循环的顺序,我们从 w[i] 更新到 t ,那么我们在更新 f[j] 时, f[j-w[i]] 已经更新过了,它并不是原来的 f[j-w[i]] ,可能 f[j-w[i]] 已经采了第 j 种草药,然后我们更新 f[j] 时调用 f[j-w[i]] ,又采了一次这种草药。这已经不符合01背包的要求了。

                所以为了防止这种事情发生,我们第2层循环可以反过来遍历,这样就解决了问题。

                代码:

#include<bits/stdc++.h>
using namespace std;
int v[1010],w[1010];
int f[1010];
int main()
{
	int t,m;
	cin>>t>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>w[i]>>v[i];
	}
	for(int i=1;i<=m;i++)
	{
		for(int j=t;j>=0;j--)
		{
			if(j>=w[i])
			{
				f[j]=max(f[j],f[j-w[i]]+v[i]);
			}
		}
	}
	cout<<f[t];
	return 0;
}

                常数优化

                优化

                刚刚的代码判断了 j 是否大于等于 w[i] ,再进行dp。但是我们发现对于j<w[i]的情况,我们完全不需要遍历。可以直接从t到w[i]。

for(int i=1;i<=m;i++)
{
	for(int j=t;j>=w[i];j--)
	{
		f[j]=max(f[j],f[j-w[i]]+v[i]);
	}
}

 二、AC代码

#include<bits/stdc++.h>
using namespace std;
int v[1010],w[1010],f[1010];
int main() {
	int t,m;
	cin>>t>>m;
	for(int i=1; i<=m; i++)
	{
		cin>>w[i]>>v[i];
	}
	for(int i=1; i<=m; i++)
	{
		for(int j=t; j>=w[i]; j--)
		{
			f[j]=max(f[j],f[j-w[i]]+v[i]);
		}
	}
	cout<<f[t];
	return 0;
}

  • 21
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值