[luogu p1616] 疯狂的采药 & 完全背包学(复)习笔记

传送门

疯狂的采药

题目背景

此题为NOIP2005普及组第三题的疯狂版。 此题为纪念LiYuxiang而生。

题目描述

LiYuxiang是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:

"孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。"

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

此题和原题的不同点:

  1. 每种草药可以无限制地疯狂采摘。
  2. 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

输入输出格式

输入格式

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

输出格式

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

输入输出样例

输入 #1

70 3
71 100
69 1
1 2

输出 #1

140

说明/提示

对于30%的数据,M <= 1000;

对于全部的数据,M <= 10000,且M*T<10000000(别数了,7个0)。

加油LiYuxiang,第一个AC留给你!

分析

此题是一道裸完全背包。
01背包不同的是,完全背包一种东西可以无限制的取用。那么假设我采了\(n\)遍这次草药,那么这种情况下的状态转移方程应该是dp[i][j] = dp[i - 1][j - n * v[i]] + n * w[i]
如果我们取整体的状态转移方程,套个\(\max\)就可以了:
dp[i][j] = max(dp[i][j],dp[i - 1][j - n * v[i]] + n * w[i])
不过如果这样,时间复杂度就是\(O(MT^T)\),此题的数据范围是\(M \le 10000\)\(T \le 100000\),所以你用这种方法能T飞。

那怎么办?

只需要把01背包中内部循环j的逆序改为顺序即可。如果你不知道01背包的一维优化中为什么是逆序,见我写的这篇题解

然后这个又怎么跟01背包扯上关系呢?首先01背包所依赖的子结果为dp[i - 1][j - v[i]],因为01背包只能选和不选两种,因此选的状态转移方程中不能含有当前的第i件物品。也就是说,缩成一维的时候要保证dp[j - v[i]]逆序遍历,才能保证遍历dp[j]的时候dp[j - v[i]]还是上一层的状态(未更新的状态)。

但是完全背包可不一样,爱选多少选多少,所以我们考虑加选一件第i种物品的策略,就需要考虑dp[i][j - v[i]]这种子结果。也就是说,完全背包的二维版本中,状态转移方程把dp[i - 1][j - v[i]]变为了dp[i][j - v[i]]。这次缩成一维,遍历到dp[j]的时候,要要求dp[j - v[i]]是更新的。这就需要正序遍历啦~

也就是说,就是一个倒序变正序。神奇吧?这就是dp的魅力。

代码

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2020-03-12 20:54:13 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2020-03-13 01:28:22
 */
#include <iostream>
#include <cstdio>

const int maxm = 10005;
const int maxt = 100005;

inline int max(int a,int b) {
    return a > b ? a : b;
}

int v[maxm],w[maxm],dp[maxt];

int main() {
    int T,M;
    std :: cin >> T >> M;
    for(int i = 1; i <= M; i++) 
        std :: cin >> v[i] >> w[i];
    
    for(int i = 1; i <= M; i++)
        for(int j = v[i]; j <= T; j++)
            dp[j] = max(dp[j],dp[j - v[i]] + w[i]);
        
    std :: cout << dp[T] << std :: endl;
    return 0;
}

评测结果

RE 44R31673389(原因:看错数据范围……弄混数组大小……)
AC 100R31673423(这个代码虽然AC了但是关于数组大小方面有点小问题)
AC 100R31680243

猜你喜欢

转载自www.cnblogs.com/crab-in-the-northeast/p/luogu-p1616.html