1616 疯狂的采药(完全背包问题)

难度:普及-

题目类型:动规

提交次数:1

涉及知识:背包动规

题目背景

此题为NOIP2005普及组第三题的疯狂版。

此题为纪念LiYuxiang而生。

题目描述

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

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

此题和原题的不同点:

1.每种采药可以无限制地疯狂采摘。

2.药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

输入输出格式

输入格式:

输入第一行有两个整数T(1 <= T <= 100000)和M(1 <= M <= 10000),用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到10000之间(包括1和10000)的整数,分别表示采摘某种草药的时间和这种草药的价值。

 

输出格式:

输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

 

代码:

 1 #include<iostream>
 2 using namespace std;
 3 int f[100010];
 4 int time[10010];
 5 int value[10010];
 6 int main(){
 7     int t, m;
 8     cin>>t>>m;
 9     int i, j;
10     for(i = 1; i <= m; i++)
11         cin>>time[i]>>value[i];
12     for(i = 1; i <= m; i++)
13         for(j = time[i]; j <= t; j++)
14             if(f[j]<f[j-time[i]]+value[i]) f[j] = f[j-time[i]]+value[i];
15     cout<<f[t];
16     return 0;
17 }

备注:

见识浅陋。因为这道题才知道了除了01背包以外还有“完全背包”这种概念,及每种物品可以无限取。关于完全背包问题,参考某博客内的内容:


 

完全背包:

完全背包(CompletePack): 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

完全背包按其思路仍然可以用一个二维数组来写出:

f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}

同样可以转换成一维数组来表示:

伪代码如下:

for i=1..N
    for v=0..V
        f[v]=max{f[v],f[v-c[i]]+w[i]}

顺序!

想必大家看出了和01背包的区别,这里的内循环是顺序的,而01背包是逆序的。
现在关键的是考虑:为何完全背包可以这么写?
在次我们先来回忆下,01背包逆序的原因?是为了是max中的两项是前一状态值,这就对了。
那么这里,我们顺序写,这里的max中的两项当然就是当前状态的值了,为何?
因为每种物品都是无限的。当我们把i从1到N循环时,f[v]表示容量为v在前i种物品时所得的价值,这里我们要添加的不是前一个物品,而是当前物品。所以我们要考虑的当然是当前状态。


 可是,谁让我迟钝呢qwq。就算讲得如此详细,我还是没有理解“顺序-当前状态”的根本含义,于是我又思考了很久,才勉强算是茅塞顿开。

我本来奇怪简化后k哪去了,其实,不管前一个物品选了多少,对选当前物品都是没有影响的,这也是无后效性的体现。与01背包不同,我们在选当前物品的时候,不再考虑没放这种物品时的情况,因为每个物品是可以无限拿的,所以现在背包里有什么就是什么,有多少就是多少,在此基础上进行考虑,对于第i种物品,只有有足够的空余空间,就可以一直往里放。这是为什么要顺序,也是为什么要在“当前状态下考虑”的含义。

"转移"的感觉要有,从前i-1个物品转移到前i个物品,从剩余空间为j到剩余空间为j+1。

另外吐槽一下,一天之内碰到两道卡常数的题也是简直了。标黄部分如果用max()是会超时的。(翻白眼.jpg)


 

靠,神犇就是不一样,下面这段话摘自背包九讲,把我自己默默想了半天,又在上面叨逼叨了半天还没说清楚的事,一小段话就说清楚了,超级佩服!:

你会发现,这个伪代码与P01的伪代码只有v的循环次序不同而已。为什么这样一改就可行呢?首先想想为什么P01中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v= 0..V的顺序循环。这就是这个简单的程序为何成立的道理。


 

posted @ 2016-10-05 21:05  timeaftertime  阅读(421)  评论(0编辑  收藏  举报