SSL1045 采药(2)


原题链接

外网进不去

题目大意

详情请进我的第一篇SSL1045博客

S a m p l e \mathbf{Sample} Sample I n p u t \mathbf{Input} Input

70 3
71 100//时间和价值
69 1
1 2

S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output

3

H i n t & E x p l a i n \mathbf{Hint\&Explain} Hint&Explain
取第二个和第三个草药,时间为69+1=70,价值为2+1=3

解题思路

本题有新的一种方法 滚 动 数 组 滚动数组
顺推和逆推的思路我在第一篇已经讲过了,那么可以发现,我们在顺推使用二维数组推答案的时候,只用到了 d p i , j dp_{i,j} dpi,j d p i − 1 , j dp_{i-1,j} dpi1,j,所以为何不能只存储这两个数组进行运算呢?
方法如下:
p r e j pre_j prej为原来的 d p i − 1 , j dp_{i-1,j} dpi1,j d p j dp_j dpj为原来的 d p i , j dp_{i,j} dpi,j

  1. d p dp dp数组里的数拷贝到 p r e pre pre数组中,即记录 d p i − 1 , j dp_{i-1,j} dpi1,j
  2. p r e j pre_j prej当做 d p i − 1 , j dp_{i-1,j} dpi1,j代入原来顺推时的状态转移方程就可推出答案。

而最后的答案就是循环过后的 d p m dp_{m} dpm,就是原来的 d p n , m dp_{n,m} dpn,m

上代码

#include<iostream>
#include<cstring>
using namespace std;

int n,m;
int w[110],c[110];
int dp[1010],pre[1010];

int main()
{
    memset(dp,0,sizeof(dp));
    cin>>m>>n;
    for(int i=1; i<=n; i++) cin>>w[i]>>c[i];
    for(int i=1; i<=n; i++)
    {
        memcpy(pre,dp,sizeof(dp));
        for(int j=m; j>=w[i]; j--)
        {
            dp[j]=std::max(pre[j],pre[j-w[i]]+c[i]);
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}

其他问题

既然可以做出最大价值,那么对应最大价值的当然就有一个最优的方案。
我不知道会不会有一些毒瘤好心帮助采药人的题目会让你输出采药的方案,所以这里还是说一下吧。
题 目 大 意 \Large{题目大意}
基本和原题一样,但是多了一个输出要求:最优的方案。
S a m p l e \mathbf{Sample} Sample I n p u t \mathbf{Input} Input

70 3
71 100//时间和价值
69 1
1 2

S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output

3
2 3

H i n t & E x p l a i n \mathbf{Hint\&Explain} Hint&Explain
取第二个和第三个草药,时间为69+1=70,价值为2+1=3

解 题 思 路 \Large{解题思路}

用顺推二维 d p dp dp先做出来结果,重要的部分就是最后的输出了。
先说方法:

  • i i i 1 ∼ n 1\sim n 1n循环,如果 d p i , m ≠ d p i − 1 , m dp_{i,m}\ne dp_{i-1,m} dpi,m=dpi1,m,就输出 i i i
    for(int i=1; i<=n; i++)
        if(dp[i][m]!=dp[i-1][m]) cout<<i<<" ";
    cout<<endl;

原理:
i i i开始循环每一个物品,他对应的 d p i , m dp_{i,m} dpi,m只可能从 d p i − 1 , m dp_{i-1,m} dpi1,m d p i − 1 , m − w i + c i dp_{i-1,m-w_i}+c_i dpi1,mwi+ci得来,所以如果 d p i , m dp_{i,m} dpi,m不变,那么就是从 d p i − 1 , m dp_{i-1,m} dpi1,m推来的,即没有选 i i i物品。如果变了,就是从 d p i − 1 , m − w i + c i dp_{i-1,m-w_i}+c_i dpi1,mwi+ci推来,即选了 i i i物品。所以只要 d p i , m dp_{i,m} dpi,m d p i − 1 , m dp_{i-1,m} dpi1,m不一样,就输出 i i i

上 代 码 \Large{上代码}

#include<iostream>
#include<cstring>
using namespace std;

int n,m;
int w[110],c[110];
int dp[110][1010];

int main()
{
    memset(dp,0,sizeof(dp));
    cin>>m>>n;
    for(int i=1; i<=n; i++) cin>>w[i]>>c[i];
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=m; j++)
        {
            if(j>=w[i])
                dp[i][j]=std::max(dp[i-1][j],dp[i-1][j-w[i]]+c[i]);
            else   
                dp[i][j]=dp[i-1][j];
        }
    }
    cout<<dp[n][m]<<endl;
    for(int i=1; i<=n; i++)
        if(dp[i][m]!=dp[i-1][m]) cout<<i<<" ";
    cout<<endl;
    return 0;
}

完美切题~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值