DP 1 01背包问题 例题洛谷p1048

题目可到洛谷查看
为采药

思路:
本题为典型的01背包问题,可以通过动态规划的方式,将取第i件物品且总空间只有i时的最大价值算出,而且在dp过程中,已经算出的数据就不会再改变,每次考虑第i件物品放不放时,其实只有两种选择,放或不放,不过不放的话dp[i][j]=dp[i-1][j]即没有放入,价值和原来一样,如果放入则要判断一下当前的价值是否是最大价值,即dp[i][j]=max(d[i-1][j-w[i]]+v[i],d[i-1][j])取两者之间最大值即可于是便可以得到最基础的代码(一开始学的时候总是不理解为啥dp出的结果一定是最优的,其实就是一个由低到高逐渐累加的过程,后面的运算要用到前面的结果)

#include <iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int val[105][1005];
int tim[105];
int ff[105];
int main()
{
    int t,m;
    cin>>t>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>tim[i]>>ff[i];
    }
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=t;j++)
        {
            if(j<tim[i])
                val[i][j]=val[i-1][j];
            else
                val[i][j]=max(val[i-1][j-tim[i]]+ff[i],val[i-1][j]);
        }
    }
    printf("%d\n",val[m][t]);
    return 0;
}

之后是在题解中看到的一维dp
一维dp省略了选择前几个物品,而且在对空间进行遍历的时候要从大到小进行遍历,因为如果不这样的话就会出现dp的值不是只由一个值决定的情况(或者说是一个物品被拿了多次的情况)之后在dp完成后dp[最大值]即是我们想要的最大价值。

#include <iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int dp[1005];
int v[105];
int w[105];
int main()
{
    int t,m;
    cin>>t>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>w[i]>>v[i];
    }
    for(int i=1;i<=m;i++)
    {
        for(int j=t;j>=0;j--)
        {
            if(j>=w[i])
                dp[j]=max(dp[j-w[i]]+v[i],dp[j]);
        }
    }
    printf("%d\n",dp[t]);
    return 0;
}

2021/4/29更新
对于要装满的 01 背包
新疆省赛(虚无的后缀)
链接:https://ac.nowcoder.com/acm/contest/15589/H
来源:牛客网

题目描述
给出 n 个数字,第 i 个数字为 a[i],我们从中选出 k 个数字,使得乘积后缀 0 的个数最多。
输入描述:
第一行,两个正整数 n,k(1 \leq k \leq n \leq 200)n,k(1≤k≤n≤200),第 2 行 n 个正整数表示 a_i(a_i \leq 10^{18})a
i

(a
i

≤10
18
)
输出描述:
输出一个整数,表示最多有多少个后缀 0
示例1
输入

2 2
20 5
输出

2
示例2
输入

3 2
2 5 20
输出

2

题解:
有多少个0,即看一个数分解后的质因子中2和5的最小值即可,故

本题可以看做是一个要装满的 01 背包 即可以先将之理解为 dp[i][j][k] 即对于前i个物品,剩余了5的个数时(可以看做01背包的重量)2的最大个数,因为眼严格算出5和2的个数,所以要求5的个数要全部被用完,即要求装满的完全背包,又因为如果直接dp三维数组可能会爆掉,所以可以将其压成一维的形式(注意要从大到小进行,因为大的值会被小的不断更新)于是就可以有如下代码

#include<cstdio>
#include<iostream>

using namespace std;
typedef long long ll;
int sum2[205];
int sum5[205];
int all;
int dp[205][205*30];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        ll x;
        cin>>x;
        while(x%2==0)  //统计2的个数
        {
            sum2[i]++;
            x/=2;
        }
        while(x%5==0)  //统计5的个数
        {
            sum5[i]++;
            x/=5;
        }
        all+=sum5[i];  //以5的个数作为背包容量
    }
    for(int i=0;i<=n;i++)
        for(int j=0;j<=all;j++)
            dp[i][j]=-2e9;   //因为要装满,所以先将所有可能置位负无穷,并保留初始状
    dp[0][0]=0;            //让数据只能从初始状态转移
    for(int i=1;i<=n;i++)
        for(int j=m;j>=1;j--)
            for(int k=all;k>=sum5[i];k--)
                dp[j][k]=max(dp[j-1][k-sum5[i]]+sum2[i],dp[j][k]);//即为前i个物品,选了j个时的 2的最大个数
    int ans=0;
    for(int i=1;i<=all;i++) ans=max(ans,min(dp[m][i],i)); //答案即为2和5最小值中的最大值
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值