题目描述:![题目](https://i-blog.csdnimg.cn/blog_migrate/21ba6f21803e45d2852ef00b5aba1901.png#pic_center)
思路:
设sum为所有参考书价格总和,题目可以理解为在sum-x价格内,最大化被删除的书价格总和,这样就可以把这个问题看作经典的01背包问题
01背包(动态规划)
- 为什么要动态规划?
这个题的本质是对每本书选择删不删掉的问题,搜索全部状态的暴力解法是O(2^n),显然是不够高效的,如何优化呢?
在搜索的过程中,删掉前面一些书产生的中间态,对后面的选择的影响可能是等效的,例如前三本书分别是10,20,30,我们选择第一本书和第二本书删掉的情况,与单独选择删掉第三本书的情况是等效的,对于第四本书来说,都是已经删掉30元的书,后面删掉书的价格和上限都是sum-x-30,基于这个,我们不难发现,前i本书删掉的价格总和只要是相等的,对第i+1本书产生的选择影响就是一样的。 - 实现动态规划
基于上面的思想,我们可以令dp[i][j]为前i本书在j的价格上限内,可以选择删掉书最大的价格总和,则dp[i][j]可以代表所有前i本书删掉一部分后,上限还剩j(这里的j就是上面例子的sum-x-30)的状态。
状态转移方程:
dp[i][j]=max(dp[i-1][j-a[i]]+a[i],dp[i-1][j])
即第i本书的选择与不选择
代码
#include<bits/stdc++.h>
using namespace std;
#define ufor(i,a,n) for(int i=a;i<=n;++i)
#define dfor(i,n,a) for(int i=n;i>=a;--i)
#define el "\n"
const int MN=33;
const int MP=1e5+5;
int a[MN];
int dp[MN][MP]; //dp[i][j]:前i个物品中在价值j内最多可以有价值和
int main()
{
int n,x;
cin>>n>>x;
int sum=0;
ufor(i,1,n){
cin>>a[i];
sum+=a[i];
}
ufor(i,1,n){
ufor(j,1,sum-x){
if(j>=a[i]){
dp[i][j]=max(dp[i-1][j],dp[i-1][j-a[i]]+a[i]);
}else
dp[i][j]=dp[i-1][j];
}
}
cout<<sum-dp[n][sum-x]<<endl;
return 0;
}
空间优化
dp[i]只用了一轮循环,可以被dp[i+1]重复利用
- 注意内层循环要掉转方向,因为状态转移是从j更小的转移到j更大的,如果不倒着遍历,j小的先被更新,j大的将使用的是本轮的内容进行更新,而不是上轮的,只有从j大开始更新,由于此时j小的还未被更新,存的是上轮的结果,才能达到上面代码的效果。
空间优化代码
#include<bits/stdc++.h>
using namespace std;
#define ufor(i,a,n) for(int i=a;i<=n;++i)
#define dfor(i,n,a) for(int i=n;i>=a;--i)
#define el "\n"
const int MN=33;
const int MP=1e5+5;
int a[MN];
int dp[MP];
int main()
{
int n,x;
cin>>n>>x;
int sum=0;
ufor(i,1,n){
cin>>a[i];
sum+=a[i];
}
ufor(i,1,n){
dfor(j,sum-x,a[i]){
dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
}
}
cout<<sum-dp[sum-x]<<endl;
return 0;
}
一点点小心得
动态规划本质上还是状态的转移,这题的状态就是删掉书的总价格,改变状态的方式是多删掉某本书,总价格就变了,所处的状态就变了,我们要做的就是在一个上限sum-x下,通过删掉不同书来改变这个状态,最后找到最接近上限的状态。
最后的代码可能就可以这样理解,dp[j]代表着这个上限,dp[a[i]]~dp[sum-x]代表着所有可能出现的上限的状态,通过n个物品的选择性删除不断在这些状态中转移,最后求得sum-x上限下,最大的删除价值和。(只是感觉,不必纠结这短话)