CSP-202209-2何以包邮?

原题链接

题目描述:题目

思路:

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上限下,最大的删除价值和。(只是感觉,不必纠结这短话)

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值