题目可到洛谷查看
为采药
思路:
本题为典型的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;
}