【算法挨揍日记】day41——【模板】01背包、416. 分割等和子集
2024-01-03 13:35:16
【模板】01背包_牛客题霸_牛客网你有一个背包,最多能容纳的体积是V。 现在有n个物品,第i个物品的体积为 ,。题目来自【牛客题霸】https://www.nowcoder.com/practice/fd55637d3f24484e96dad9e992d3f62e?tpId=230&tqId=2032484&ru=/exam/oj&qru=/ta/dynamic-programming/question-ranking&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D196#:~:text=%E4%BD%A0%E6%9C%89%E4%B8%80%E4%B8%AA%E8%83%8C%E5%8C%85,%E6%A8%A1%E6%9D%BF%E3%80%9101%E8%83%8C%E5%8C%85【模板】01背包_牛客题霸_牛客网
题目描述:
描述
你有一个背包,最多能容纳的体积是V。
现在有n个物品,第i个物品的体积为��vi??,价值为��wi?。
(1)求这个背包至多能装多大价值的物品?
(2)若背包恰好装满,求至多能装多大价值的物品?
输入描述:
第一行两个整数n和V,表示物品个数和背包体积。
接下来n行,每行两个数��vi?和��wi?,表示第i个物品的体积和价值。
1≤�,�,��,��≤10001≤n,V,vi?,wi?≤1000
输出描述:
输出有两行,第一行输出第一问的答案,第二行输出第二问的答案,如果无解请输出0。
解题思路:
算法思路:
背包问题的状态表??常经典,如果?家不知道怎么来的,就把它当成?个「模板」记住吧~
我们先解决第?问:
1.
状态表?:
dp[i][j]
表?:从前
i
个物品中挑选,总体积「不超过」
j
,所有的选法中,能挑选出来
的最?价值。
2.
状态转移?程:
线性
dp
状态转移?程分析?式,?般都是根据「最后?步」的状况,来分情况讨论:
i.
不选第
i
个物品:相当于就是去前
i - 1
个物品中挑选,并且总体积不超过
j
。此
时
dp[i][j] = dp[i - 1][j]
;
ii.
选择第
i
个物品:那么我就只能去前
i - 1
个物品中,挑选总体积不超过
j - v[i]
的物品。此时
dp[i][j] = dp[i - 1][j - v[i]] + w[i]
。但是这种状态不?
定存在,因此需要特判?下。
综上,状态转移?程为:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] +
w[i])
。
3.
初始化:
我们多加??,?便我们的初始化,此时仅需将第??初始化为
0
即可。因为什么也不选,也能
满?体积不?于
j
的情况,此时的价值为
0
。
4.
填表顺序:
根据「状态转移?程」,我们仅需「从上往下」填表即可。
5.
返回值:
根据「状态表?」,返回
dp[n][V]
。
接下来解决第?问:
第?问仅需微调?下
dp
过程的五步即可。
因为有可能凑不?
j
体积的物品,因此我们把不合法的状态设置为
-1
。
1.
状态表?:
dp[i][j]
表?:从前
i
个物品中挑选,总体积「正好」等于
j
,所有的选法中,能挑选出
来的最?价值。
2.
状态转移?程:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i])
。
但是在使?
dp[i - 1][j - v[i]]
的时候,不仅要判断
j >= v[i]
,?要判断
dp[i
- 1][j - v[i]]
表?的情况是否存在,也就是
dp[i - 1][j - v[i]] != -1
。
3.
初始化:
我们多加??,?便我们的初始化:
i.
第?个格?为
0
,因为正好能凑?体积为
0
的背包;
ii.
但是第??后?的格?都是
-1
,因为没有物品,?法满?体积?于
0
的情况。
4.
填表顺序:
根据「状态转移?程」,我们仅需「从上往下」填表即可。
5.
返回值:
由于最后可能凑不成体积为
V
的情况,因此返回之前需要「特判」?下。
(别急着?开,后?还有优化)
解题代码:
#include<iostream>
#include<vector>
using namespace std;
int n,V;
const int N=1010;
vector<int>v(N);
vector<int>w(N);
vector<vector<int>>dp1(N,vector<int>(N,0));
vector<vector<int>>dp2(N,vector<int>(N,0));
int main()
{
cin>>n>>V;
for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=V;j++)
{
dp1[i][j]=dp1[i-1][j];
if(j-v[i]>=0) dp1[i][j]=max(dp1[i][j],dp1[i-1][j-v[i]]+w[i]);
}
}
cout<<dp1[n][V]<<endl;
for(int i=1;i<=V;i++)dp2[0][i]=-1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=V;j++)
{
dp2[i][j]=dp2[i-1][j];
if(j-v[i]>=0&&dp2[i-1][j-v[i]]!=-1) dp2[i][j]=max(dp2[i][j],w[i]+dp2[i-1][j-v[i]]);
}
}
cout<<(dp2[n][V]==-1?0:dp2[n][V])<<endl;
return 0;
}
优化后:
#include<iostream>
#include<vector>
#include<string.h>
using namespace std;
int n,V;
const int N=1010;
vector<int>v(N);
vector<int>w(N);
int dp[N];
int main()
{
cin>>n>>V;
for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
{
for(int j=V;j>=1;j--)
{
dp[j]=dp[j];
if(j-v[i]>=0) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[V]<<endl;
memset(dp,0,sizeof(dp));
for(int i=1;i<=V;i++)dp[i]=-1;
for(int i=1;i<=n;i++)
{
for(int j=V;j>=1;j--)
{
dp[j]=dp[j];
if(j-v[i]>=0&&dp[j-v[i]]!=-1) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<(dp[V]==-1?0:dp[V])<<endl;
return 0;
}
416. 分割等和子集
题目描述:
给你一个?只包含正整数?的?非空?数组?nums
?。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
解题思路:
算法思路:
先将问题转化成我们「熟悉」的题型。
如果数组能够被分成两个相同元素之和相同的?集,那么原数组必须有下??个性质:
i.
所有元素之和应该是?个偶数;
ii.
数组中最?的元素应该?于所有元素总和的?半;
iii.
挑选?些数,这些数的总和应该等于数组总和的?半。
根据前两个性质,我们可以提前判断数组能够被划分。根据最后?个性质,我们发现问题就转化成
了「01 背包」的模型:
i.
数组中的元素只能选择?次;
ii.
每个元素?临被选择或者不被选择的处境;
iii.
选出来的元素总和要等于所有元素总和的?半。
其中,数组内的元素就是物品,总和就是背包。
那么我们就可以?背包模型的分析?式,来处理这道题。
请?家注意,「不要背」状态转移?程,因为题型变化之后,状态转移?程就会跟着变化。我们要
记住的是分析问题的模式。?这种分析问题的模式来解决问题。
1.
状态表?:
dp[i][j]
表?在前
i
个元素中选择,所有的选法中,能否凑成总和为
j
这个数。
2.
状态转移?程:
?规矩,根据「最后?个位置」的元素,结合题?的要求,分情况讨论:
i.
不选择
nums[i]
:那么我们是否能够凑成总和为
j
,就要看在前
i - 1
个元素中
选,能否凑成总和为
j
。根据状态表?,此时
dp[i][j] = dp[i - 1][j]
;
ii.
选择
nums[i]
:这种情况下是有前提条件的,此时的
nums[i]
应该是?于等于
j
。
因为如果这个元素都?要凑成的总和?,选择它就没有意义呀。那么我们是否能够凑成总和
为
j
,就要看在前
i - 1
个元素中选,能否凑成总和为
j - nums[i]
。根据状态表
?,此时
dp[i][j] = dp[i - 1][j - nums[i]]
。
综上所述,两种情况下只要有?种能够凑成总和为
j
,那么这个状态就是
true
。因此,状态转
移?程为:
dp[i][j] = dp[i - 1][j]
if(nums[i - 1] <= j) dp[i][j] = dp[i][j] || dp[i - 1][j -
nums[i]]
3.
初始化:
由于需要?到上??的数据,因此我们可以先把第??初始化。
第??表?不选择任何元素,要凑成?标和
j
。只有当?标和为
0
的时候才能做到,因此第?
?仅需初始化第?个元素
dp[0][0] = true
4.
填表顺序:
根据「状态转移?程」,我们需要「从上往下」填写每??,每??的顺序是「?所谓的」。
5.
返回值:
根据「状态表?」,返回
dp[n][aim]
的值。
其中
n
表?数组的??,
aim
表?要凑的?标和。
6.
空间优化:
所有的「背包问题」,都可以进?空间上的优化。
对于 01背包类型的,我们的优化策略是:
i.
删掉第?维;
ii.
修改第?层循环的遍历顺序即可。
?解题代码:
class Solution {
public:
bool canPartition(vector<int>& nums) {
int n=nums.size();
int sum=0;
for(int i=0;i<n;i++)sum+=nums[i];
if(sum%2==1)return false;
int target=sum/2;
vector<vector<bool>>dp(n+1,vector<bool>(target+1,false));
for(int i=0;i<=n;i++)dp[i][0]=true;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=target;j++)
{
dp[i][j]=dp[i-1][j];
if(j-nums[i-1]>=0)
dp[i][j]=dp[i][j]||dp[i-1][j-nums[i-1]];
}
}
return dp[n][target];
}
};
文章来源:https://blog.csdn.net/m0_69061857/article/details/135027767
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!