这题可以看作是一个0-1背包问题,每本书的状态只有两个:选(1)与不选(0),但是要注意题目中要求书的价格总和不小于x,而0-1背包问题是物品容量不超过V,所以这里要转换一下,先求出n本书的总金额sum,再用sum-x,得到的就是不应该买的书的最大金额。而不应该买的书的金额越大,满足条件应该买的书的金额就越小。
对于dp[i][j]的状态表示,集合为从第1~i本书里选择一些书,其总金额不超过j;属性为总金额的最大值。
可以得出状态转移方程:
dp[i][j]=dp[i-1][j], j<a[i]
dp[i][j]=max(dp[i-1][j],dp[i-1][j-a[i]]+a[i]), j>=a[i]
完整代码如下:
#include <iostream>
using namespace std;
int a[35];
int n,x,sum,v; //定义在全局区,默认初始化为0
int dp[35][300010]; //存的状态是从1-i本书里选不超过金额j的最大值
int main()
{
cin>>n>>x;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum+=a[i];
}
v=sum-x; //背包空间 应该买的书的金额大于等于x元,则不应该买的书的金额要小于等于sum-x元
for(int i=1;i<=n;i++)
{
for(int j=1;j<=v;j++)
{
dp[i][j]=dp[i-1][j]; //不选
if(j>=a[i])
{
dp[i][j]=max(dp[i][j],dp[i-1][j-a[i]]+a[i]);
//求max是因为不能买的书的金额越大,能买的书的金额就越小
}
}
}
int res=sum-dp[n][v]; //注意一开始算的是不能买的书的金额
cout<<res;
return 0;
}
运行结果:
这里已经能得满分了。
动态规划时间复杂度一般可以这样计算:状态数量*状态转移计算量。
对0-1背包问题,可以对它进行一维优化,降低时间复杂度,优化后的代码如下:
#include <iostream>
using namespace std;
int a[35];
int n,x,sum,v;
int dp[300010];
int main()
{
cin>>n>>x;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum+=a[i];
}
v=sum-x; //背包空间 买的书的金额大于等于x元,则不能买的书的金额要小于等于sum-x元
for(int i=1;i<=n;i++)
{
for(int j=v;j>=a[i];j--) //注意一定要逆序,否则就变成完全背包问题了
{
dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
//求max是因为不能买的书的金额越大,能买的书的金额就越小
}
}
int res=sum-dp[v]; //注意一开始算的是不能买的书的金额
cout<<res;
return 0;
}
运行结果对比:
这里浅谈一下我所理解的优化过程,在最开始的版本中有:
for(int i=1;i<=n;i++)
{
for(int j=1;j<=v;j++)
{
dp[i][j]=dp[i-1][j];
if(j>=a[i])
{
dp[i][j]=max(dp[i][j],dp[i-1][j-a[i]]+a[i]);
}
}
}
要变成一维,首先先删去第一维:
for(int i=1;i<=n;i++)
{
for(int j=1;j<=v;j++)
{
dp[j]=dp[j];
if(j>=a[i])
{
dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
}
}
}
dp[j]=dp[j]很明显可以省略,且j只有满足j>=a[i]时if语句才执行,故再进行简化:
for(int i=1;i<=n;i++)
{
for(int j=a[i];j<=v;j++)
{
dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
}
}
这样写其实还有一个问题,由于j-a[i],所以dp[j-a[i]]会在dp[j]之前先被算出,实际上是第i层的状态而不是第i-1层的状态,上面的代码其实等价于dp[i][j]=max(dp[i][j],dp[i][j-a[i]]+a[i]);这样可能导致一个物品多次放入背包,这就不是完全背包问题了。故j应该逆序:
for(int i=1;i<=n;i++)
{
for(int j=v;j>=a[i];j--) //一定要逆序
{
dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
}
}
这样同一个物品就不会重复放入背包了,得到的结果才是正确的。
如果不是很理解,可以将结果打印输出一下,还是很明显的。
结语:这是我在学完Acwing算法基础课之后再重做去年考的csp认证题目,当时大部分人和我一样都是15个for循环暴力求解得70分,但其实只要掌握一些基础算法还是很好拿满分的。