[动态规划初步][01揹包问题][P1048 采药]做题思路和总结

NOIP2005普及组第三题 采药 :

题目描述

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

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

输入输出格式

输入格式:

 

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

 

输出格式:

 

1个整数,表示在规定的时间内可以采到的草药的最大总价值。

基本思路:

这是最基础的揹包问题,特点是:每种物品仅有一件,可以选择放或不放。

用子问题定义状态:即DP[i][v]表示前i件物品恰放入一个容量为v的揹包可以获得的最大价值。则其状态转移方程便是:

DP[i][v]=max(DP[i-1][v],DP[i-1][v-c[i]]+w[i])

最基本的想法是:遍历0->M,再遍历0->T,利用状态转移方程计算出考虑i种药品,时间(揹包容量)为j的情况下的最优解。最后输出DP[M][T]即为整体的最优解。

这也是动态规划算法的基本特征: 最优化原理与无后效性。

 代码:

#include <iostream>
#include <algorithm>

using namespace std;

int DP[101][1001] = {{0}};

int Val[101];
int Time[101];

int main(void)
{
    int T,M;
    cin >> T >> M;
    for(int i = 1;i <= M;i++)
    {
        cin >> Time[i] >> Val[i];
    }
    
    for(int i = 1;i <= M;i++)
    {
        for(int j = 0;j <= T;j++)
        {
            DP[i][j] = DP[i-1][j];
            if(j >= Time[i])
            {
                DP[i][j] = max(DP[i][j],DP[i-1][j-Time[i]]+Val[i]);
            }
        }
    }

    cout << DP[M][T] << endl;
    return 0;
}

空间复杂度优化:以上方法的时间和空间复杂度均为O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到O(V)。

    如果只用一个数组DP[0..V],能不能保证第i次循环结束后DP[v]中表示的就是我们定义的状态DP[i][v]呢?DP[i][v]是由DP[i-1][v]和DP[i-1][v-c[i]]两个子问题递推而来,能否保证在推DP[i][v]时(也即在第i次主循环中推DP[v]时)能够得到DP[i-1][v]和DP[i-1][v-c[i]]的值呢?事实上,这要求在每次主循环中我们以v=V..0的顺序推f[v],这样才能保证推DP[v]时DP[v-c[i]]保存的是状态DP[i-1][v-c[i]]的值。

核心思想:通过调整遍历决策运算的顺序,保证推DP[v]DP[v-c[i]]保存的是状态DP[i-1][v-c[i]]的值。

此时的状态转移方程和伪代码:

for i=1..N

    for v=V..0

        DP[v]=max(DP[v],DP[v-c[i]]+w[i]);

代码: 

 

#include <iostream>
#include <algorithm>

using namespace std;

int DP[1001] = {0};

int Val[101];
int Time[101];

int main(void)
{
    int T,M;
    cin >> T >> M;
    for(int i = 1;i <= M;i++)
    {
        cin >> Time[i] >> Val[i];
    }
    
    for(int i = 1;i <= M;i++)
    {
        for(int j = T;j >= 0;j--)
        {
            if(j >= Time[i])
            {
                DP[j] = max(DP[j],DP[j-Time[i]]+Val[i]);
            }
        }
    } 

    cout << DP[T] << endl;
    return 0;
}

 

点赞