前言:01型背包问题(二维)的链接:
https://blog.csdn.net/qq_56430444/article/details/118160876
完全背包问题链接:
https://blog.csdn.net/qq_56430444/article/details/118389012
01型背包问题的模板(一维):
1.状态:dp[i]表示为背包容量为i所能装下的最大价值
2.状态转移方程: if( i>=w[j] ){ dp[i] = max ( dp[i-w[j]]+v[j] , dp[i] )
3.循环方向: for(枚举物品的编号,正向)
for(枚举背包的容量,逆向) 注意:这里是一般情况,个别情况请单独分析
决策(状态转移方程)
4.初始化:看题目而定
01型背包问题经典例题(一维):
1. (模板题)采药:最纯正的01型背包问题
2.小A点菜(求恰好装满背包容量的方案数目的01型背包问题):
#include<bits/stdc++.h>
using namespace std;
main(){
int n,m;
cin>>n>>m;
int food[n+1]={0};
int f[m+1]={0};
for(int i=1;i<=n;i++){
cin>>food[i];
}
f[0]=1;
for(int i=1;i<=n;i++){
for(int j=m;j>=1;j--){
if(j>=food[i]){ // 这个大于”等于“号很关键
f[j]+=f[j-food[i]];
}
}
}
cout<<f[m];
}
3.五倍经验日: 关键点在于状态转移方程,赢了加经验,输了也加经验
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll m,n;
ll w[1005],l[1005],u[1005],dp[1005];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>l[i]>>w[i]>>u[i];
}
for(int i=1;i<=n;i++){
for(int j=m;j>=0;j--){
if(j>=u[i]){
dp[j]=max(dp[j-u[i]]+w[i],dp[j]+l[i]);
}else{
dp[j]+=l[i];
}
}
}
cout<<dp[m]*5<<endl;
}
4.最大约数和: 先要打表进行初始化,不然会超时
#include<bits/stdc++.h>
using namespace std;
int w[1005],n;
int dp[1005][1005];
int p(int x){
int sum=0;
for(int i=1;i<x;i++){
if(x%i==0){
sum+=i;
}
}
return sum;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
w[i]=p(i);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i>=j){
dp[i][j]=max(dp[i-j][j-1]+w[j],dp[i][j-1]);
}else{
dp[i][j]=dp[i][j-1];
}
}
}
cout<<dp[n][n]<<endl;
}
5.考前临时抱佛脚(这题难点是将题目理解并且转到01型背包问题):
思路:因为左右脑可以同时思考,要想时间最少,则必然左右脑的时间要趋于相同,故可以设另一个大脑的最大容量为T/2,这样就可以转化到了01背包问题。
#include<bits/stdc++.h>
using namespace std;
int num[10];
int sum=0;
int main(){
for(int i=1;i<=4;i++){
cin>>num[i];
}
for(int i=1;i<=4;i++){
int al[34]={0};
int summ=0;
for(int j=1;j<=num[i];j++){
cin>>al[j];
summ+=al[j];
}
int dp[1300][34];
for(int k=1;k<=summ/2;k++){
for(int j=1;j<=num[i];j++){
if(k>=al[j]){
dp[k][j]=max(dp[k-al[j]][j-1]+al[j],dp[k][j-1]);
}else{
dp[k][j]=dp[k][j-1];
}
}
}
sum+=summ-dp[summ/2][num[i]]; //假设我们求的是右脑(当然左脑也行,这是对称的)最大
} //时间,这个最大时间与dp数组的状态的下标含义一样,故
cout<<sum<<endl; //最大时间不超过T/2,则用总数减去右脑的最大时间数就是
} //答案
6.Subset sums 集合(求装满容量为j的背包的最大方案数的01型背包问题)
此题的转换成01背包的思路与考前零时抱佛脚的思路相同,这里就不赘述了
#include<bits/stdc++.h>
using namespace std;
int dp[1600][40];
int w[40];
int n,sum=0;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
w[i]=i;
sum+=i;
}
if(sum%2!=0){
cout<<0<<endl;
}else{
dp[0][0]=1;
for(int i=1;i<=sum/2;i++){
for(int j=1;j<=n;j++){
dp[i][j]+=dp[i][j-1];
if(i>=w[j]){
dp[i][j]+=dp[i-w[j]][j-1];
}
}
}
cout<<dp[sum/2][n]<<endl;
}
}
7.yyy2015co1u盘:(此题目将01背包和二分搜索相互结合起来)
思路:刚读题时便形成一个朴素的思路,就是将接口大小从大到小枚举,再套01型背包问题,但结合数据范围我们可以想到这样必然超时,我们可以这样想随着接口增大,则当超过某个最小数值后,一定能满足题目要求,就像这样 0000.....0111111 (0表示不满足要求,1表示满足要求) 不难想到可以利用二分搜索查找这个区间的最小值,这样就将原有的o(n)降到o(logn)
#include<bits/stdc++.h>
using namespace std;
int n,p,s;
int w[1200],v[1200];
int dp[1200][1200];
int minn=9999999,maxn=0;
int m(int x){
for(int i=1;i<=s;i++){
for(int j=1;j<=n;j++){
dp[i][j]=0;
}
}
for(int i=1;i<=s;i++){
for(int j=1;j<=n;j++){
if(x>=w[j]&&i>=w[j]){
dp[i][j]=max(dp[i-w[j]][j-1]+v[j],dp[i][j-1]);
}else{
dp[i][j]=dp[i][j-1];
}
}
}
return dp[s][n];
}
int main(){
cin>>n>>p>>s;
for(int i=1;i<=n;i++){
cin>>w[i]>>v[i];
if(w[i]>maxn){
maxn=w[i];
}
if(w[i]<minn){
minn=w[i];
}
}
int l=minn,r=maxn;
while(l<r){
int mid=l+(r-l)/2;
if(m(mid)>=p){
r=mid;
}else{
l=mid+1;
}
}
if(m(l)>=p){
cout<<l<<endl;
}else{
cout<<"No Solution!"<<endl;
}
}