zoukankan      html  css  js  c++  java
  • 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的顺序循环。这就是这个简单的程序为何成立的道理。


  • 相关阅读:
    软件工程实践总结作业
    SDN第4次上机作业
    SDN第四次作业
    SDN第三次上机作业
    SDN第三次作业
    SDN第二次上机作业
    SDN第一次上机作业
    免费自动生成字幕工具推荐,啃生肉啊(6.12更新)
    博客园美化,自定义你的博客,css+html (iframe)
    找质数、素数_算法优化(C++)
  • 原文地址:https://www.cnblogs.com/fangziyuan/p/5932989.html
Copyright © 2011-2022 走看看