背包问题

  • 一、01 Knapsack(输出路径- >选的物品)
  • 二、完全背包
    • 1、三重循环,极可能TLE,滚动数组优化后j逆向枚举
    • 2、二重,优化消去变量k(没有特别厘清,但可以直接从完全背包角度理解,取完一个还可以取无数个)
    • 3、滚动数组优化成一维数组,j正向枚举
  • 三、背包恰好装满的情形
    • 解释二则:
    • ?为什么不能初始化为0,让0和背包装满时的dp正数值进行区分呢?
  • 四、多重背包问题Ⅰ(数据小,拆成0-1背包)
    • 1、将多重背包一个一个拆出来,拆成0—1背包
    • 2、直接加一层循环,枚举每件商品的件数
  • 五、多重背包问题Ⅱ(二进制优化划分)
  • 单调队列优化
  • 六、二维背包(有两个对于背包的限制,不止容量还有体积啥的)
  • 七、分组背包
  • 混合背包
  • 题目们
    • 474. 一和零
    • 416. Partition Equal Subset Sum(解释恰好装满型)
      • 1、初始化为-1
      • 2、初始化为负无穷(`max(dp[j],dp[j-nums[i-1]]+1)`,如果`dp[j-nums[i-1]]`是不存在的状态,只要`dp[j-nums[i-1]]+1`的值比0还小,自然可以被dp[j](至少为0)比下去,要想不比较,至少初始化为-2, − 2 + 1 < 0 -2+1<0 −2+1<0
      • 3、初始化为false
      • 4、bitset优化
    • 322. Coin Change
    • 494. Target Sum
    • hdu4968 最大最小GPA
      • 暴力
      • 分组背包做法
      • 贪心做法
  • 参考

一、01 Knapsack(输出路径- >选的物品)

#include <iostream>
#include <stack>
#include <string.h>
using namespace std;
const int N=105;
int w[N];
int v[N];
int path[N][1005];//path[i][j] 1
int dp[1005];
//dp[i][j]代表有i件商品可供选择有j这么大的容量可供盛放 这时可取得的最大商品价值
int main(){ int n,m;//商品件数和背包容量 ,要取得最大的价值 cin>>n>>m;for(int i=1;i<=n;i++){cin>>w[i];}for(int i=1;i<=n;i++){cin>>v[i];}for(int i=1;i<=n;i++){for(int j=m;j>=w[i];j--){path[i][j]=0;
//          dp[j]=max(dp[j-w[i]]+v[i],dp[j]);if(dp[j-w[i]]+v[i]>dp[j]){path[i][j]=1;dp[j]=dp[j-w[i]]+v[i];}} }
//  cout<<dp[m]<<endl;stack<int> st;int i=n;int j=m;while(i>0&&j>0){if(path[i][j]==1){  st.push(i);j-=w[i];}i--;}while(!st.empty()){cout<<st.top()<<endl;st.pop();}return 0;
}

动态规划的核心思想避免重复计算在01背包问题中体现得淋漓尽致。第i件物品装入或者不装入而获得的最大价值完全可以由前面i-1件物品的最大价值决定,暴力枚举忽略了这个事实。

二、完全背包

1、三重循环,极可能TLE,滚动数组优化后j逆向枚举

 for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){//          if(j>=w[i])dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
//          else dp[i][j]=dp[i-1][j];for(int k=0;k*w[i]<=j;k++){dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);}}}

(如果这种三重循环暴力的方法不超时,且欲将其优化成一维数组,枚举j时要像0-1背包一样逆向噢,因为状态转移方程max第二项是 d p 【 i − 1 】 dp【i-1】 dp【i−1】

2、二重,优化消去变量k(没有特别厘清,但可以直接从完全背包角度理解,取完一个还可以取无数个)

a.不装入第i种物品,即 d p [ i − 1 ] [ j ] dp[i−1][j] dp[i−1][j],同01背包;
b.装入第i种物品,此时和01背包不太一样,因为每种物品有无限个(但注意书包限重是有限的),所以此时不应该转移到 d p [ i − 1 ] [ j − w [ i ] ] dp[i−1][j−w[i]] dp[i−1][j−w[i]]而应该转移到 d p [ i ] [ j − w [ i ] ] dp[i][j−w[i]] dp[i][j−w[i]],即装入第 i i i种商品后还可以再继续装入第 i i i种商品。

 for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(j>=w[i])dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]);else dp[i][j]=dp[i-1][j];}}

或者

 for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){dp[i][j]=dp[i-1][j];if(j>=w[i])dp[i][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);}}

这个状态转移方程与01背包问题唯一不同就是max第二项不是dp[i-1]而是dp[i]。

3、滚动数组优化成一维数组,j正向枚举

和01背包问题类似,也可进行空间优化,优化后不同点在于这里的 j 只能正向枚举而01背包只能逆向枚举,因为这里的max第二项是dp[i]而01背包是dp[i-1],即这里就是需要覆盖而01背包需要避免覆盖。

for(int i=1;i<=n;i++){for(int j=w[i];j<=m;j++){dp[j]=max(dp[j],dp[j-w[i]]+v[i]);}} cout<<dp[m];}

01背包和完全背包问题此解法的空间优化版解法唯一不同就是前者的 j 只能逆向枚举而后者的 j 只能正向枚举,这是由二者的状态转移方程决定的

三、背包恰好装满的情形

为什么 d p [ m ] dp[m] dp[m]是能取到得最大价值,因为一开始对dp数组进行初始化时,将dp数组所有元素都初始化为0了, d p [ m ] dp[m] dp[m]可能并非由 d p [ 0 ] dp[0] dp[0]这个状态庄毅而来,可能是由中途某个 d p [ k ] dp[k] dp[k]转移而来

如果问,恰好占用了m这么多的背包容量 这一条件下能取得得最大价值,就在初始化dp数组时,将dp【0】初始化为0,其余都初始化为负无穷

解释二则:

现在考虑动态规划的初始值问题。
在之前的问题中,dp[i][v]初始化设置为0.
因为在初始状态,背包中没有任何物品。不论背包的容量多大,里面的价值只是0.这个状态是合法的。因为背包并没有超出容量。
现在,背包只有完全占满才是合法的值。那么在初始状态,dp[i][0]=0是合法的,因为容量为0,不放任何东西就是合法了。其他的都是非法值。应该设置为负无穷。
背包问题有时候还有一个限制就是必须恰好装满背包,此时基本思路没有区别,只是在初始化的时候有所不同。

如果没有恰好装满背包的限制,我们将dp全部初始化成0就可以了。因为任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。如果有恰好装满的限制,那只应该将dp[0,…,N][0]初始为0,其它dp值均初始化为-inf,因为此时只有容量为0的背包可以在什么也不装情况下被“恰好装满”,其它容量的背包初始均没有合法的解,应该被初始化为-inf。

?为什么不能初始化为0,让0和背包装满时的dp正数值进行区分呢?

由以上图表可见,dp数组的值 d p 【 i 】 【 j 】 dp【i】【j】 dp【i】【j】表示的含义是对于前i件商品,能否装满容量为j的背包,能装满的话 d p 【 i 】 【 j 】 dp【i】【j】 dp【i】【j】表示装满时的最大价值。
何以确保装满了容量为j的背包,因为递推的过程其实是由取一件商品算出对应容量的背包,从而为dp[i]【该若干商品体积】赋予一个正的价值,那么未进行赋值的对应容量的背包自然就是不能被这些商品装满的
那么,为什么不干脆就初始化未0,从而与能被装满的背包价值来区分呢?
是因为一开始赋予一个不合法的值更符合意义(0对于容量未0的背包也是合法的)嘛?

参考链接

四、多重背包问题Ⅰ(数据小,拆成0-1背包)


1、将多重背包一个一个拆出来,拆成0—1背包

将多重背包拆成0—1背包要注意的是这时数组最大容量不能由商品种类数决定,而是总建树=种类*件数

#include <iostream>
using namespace std;
const int N=1e5;//这时数组最大容量不能由商品种类数决定,而是总建树=种类*件数
int v[N];//价值
int w[N];//重量
int dp[N];
int main(){int n,m;cin>>n>>m;int vv,ww,c;int cnt=1;//算作0-1背包的商品总建树 for(int i=1;i<=n;i++){//      cin>>w[i]>>v[i];cin>>ww>>vv>>c;for(int j=1;j<=c;j++){v[cnt]=vv;w[cnt]=ww;    cnt++;}}for(int i=1;i<=cnt;i++){for(int j=m;j>=w[i];j--){dp[j]=max(dp[j],dp[j-w[i]]+v[i]);}}cout<<dp[m];return 0;
}

2、直接加一层循环,枚举每件商品的件数

三重循环的层次不能变,可能跟滚动数组中那种求值顺序有关?×
显然是因为逻辑不对,第i件物品装入或者不装入而获得的最大价值完全可以由前面i-1件物品的最大价值和背包剩余容量决定,容量要作为选取的先决条件,必定在选取件数的外层呀

#include <iostream>
using namespace std;
const int N=1e5;//这时数组最大容量不能由商品种类数决定,而是总建树=种类*件数
int v[N];//价值
int w[N];//重量
int s[N];//每种商品的建树
int dp[N];
int main(){int n,m;cin>>n>>m;for(int i=1;i<=n;i++){cin>>w[i]>>v[i]>>s[i];}for(int i=1;i<=n;i++){for(int j=m;j>=w[i];j--){for(int k=1;k<=s[i]&&k*w[i]<=j;k++){dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);}}}
//  for(int i=1;i<=n;i++){//      for(int k=1;k<=s[i];k++){//          for(int j=m;j>=w[i];j--){//              if(k*w[i]<=j)dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);
//          }
//      }
//  }cout<<dp[m];return 0;
}

五、多重背包问题Ⅱ(二进制优化划分)

数据范围增大,三重循环会超时

对于任意一个数字来说,都可以用一个二进制来表达,如7 ,二进制为“111”,可以被划分为个数分别为1、2和4的三堆物品,但我们此时并不是完全采用二进制来划分.

给定一个数s, 问最少可以把s分成多少个数,并把这些数拼成小于等于s的所有的数?
l o g 2 S log_2S log2​S (+1)个
并非把物体分成S份,而是分成 l o g 2 S log_2S log2​S份
S
1、2、4、8……x
x=S-1-2-4-8-……
一秒可以执行 1 0 7 10^7 107以内这个复杂度的操作
除了这些以2为底的幂次数,最后剩余的非零数也要算在组成数之中

以 9 为例,先划分出一个1,再划分出 2,再划分出 4,最后剩下了一个 2,2小于8,就需要单独划分为一堆。

采用二进制思路将第 i 种物品分成 O ( l o g S i ) O(logS_i) O(logSi​)件物品,将原问题转化为了复杂度为

的 01 背包问题

#include <iostream>
#include <vector>
using namespace std;
const int N=1e5;
int v[N];//价值
int w[N];//重量
int dp[N];
struct node{int w;int v;node(int w,int v):w(w),v(v){};
};
int main(){int n,m;cin>>n>>m;int ww,vv,s;vector<node> good;for(int i=1;i<=n;i++){//一个一个拆成0-1背包 ,N`=N*s,N`*v会超时
//  有这么几个数,每个数选或不选,一定能用这几个数拼出x cin>>ww>>vv>>s;for(int k=1;k<=s;k*=2){//这s件不拆成1、1、1、1,而是1,2,4, s-=k;good.push_back(node(k*ww,k*vv));}if(s>0)good.push_back(node(s*ww,s*vv));}int len=good.size();for(int i=0;i<len;i++){for(int j=m;j>=good[i].w;j--){dp[j]=max(dp[j],dp[j-good[i].w]+good[i].v);}}cout<<dp[m];return 0;
}

单调队列优化

六、二维背包(有两个对于背包的限制,不止容量还有体积啥的)

前面讨论的背包容量都是一个量:重量。二维背包问题是指每个背包有两个限制条件(比如重量和体积限制),选择物品必须要满足这两个条件。此类问题的解法和一维背包问题不同就是dp数组要多开一维,其他和一维背包完全一样

七、分组背包

0-1背包问题,每次(对于某种物品)只有1个物品选或不选,
分组背包,每次有s个物品可供选择,
n种物品可看作总共n组,每组有si个物品,选n次,每次si+1种决策 ,因为每组物品有若干个,同一组内的物品最多只能选一个。
要么一个都不选,要么选第1个,要么选第2个,……(选一个)
每个物品组选一个出来,每个物品组的决策数是s+1

多重背包,每种物品有si件,第i种物品也有si+1个选择,选0个,选1个,选2个

多重背包是分组背包的一个特殊情况
每种物品看成一组,打包一个,打包两个
做出的决策是打包,打包后可看成一种新的容量和价值的商品,就像 增加了若干种商品之后的0-1背包
而分组背包则不能像多重背包一样转化为0-1背包,
一定要第三个循环去循环所有的决策,找出最佳决策

#include <iostream>
using namespace std;
const int N=105;
int w[N];//体积
int v[N];//价值
int dp[N][N];
int main(){int n,m;cin>>n>>m;for(int i=1;i<=n;i++){//前i组件商品 int s;cin>>s;for(int l=1;l<=s;l++){cin>>w[l]>>v[l]; }//输入这组商品的价值 for(int j=0;j<=m;j++){//当前背包容量,二维dp数组从小到大枚举ok,一维数组得
//      从大到小枚举,因为每件物品只能取一次,完全背包一维从小到大枚举dp[i][j]=dp[i-1][j];for(int k=1;k<=s;k++){//选第0件表示不选❌
//              dp[i][j]=dp[i-1][j]; if(j>=w[k])dp[i][j]=max(dp[i][j],dp[i-1][j-w[k]]+v[k]);
//              else dp[i][j]=dp[i-1][j]; }    } } cout<<dp[n][m];   return 0;

1、dp[i][j]=dp[i-1][j];为什么要写在决策循环的外面,因为要使一组内(第i组内)所有的决策进行比较,方可选出最佳决策,放在循环里面,那么不是一组之内的决策惊醒比较,而是所有的决策分别与上一组的决策值进行比较。
建议0-1背包(二维的)也写成上两行代码的形式,相当于统一一点啦

 dp[i][j]=dp[i-1][j]; if(j>=w[k])dp[i][j]=max(dp[i][j],dp[i-1][j-w[k]]+v[k]);
//  else dp[i][j]=dp[i-1][j];

2、对于背包容量的循环,只能从0开始了,没有最小的节点,因为对于第i组物品,没法直到第i组物品能装下的下限,w[i]是没有意义的
分组背包里for(int j=w[i];j<=m;j++)
当然了,可以先枚举决策,再枚举对应范围内容量的背包
3、我们说每组物品有s件,那么就有s+1种决策,但是枚举这所有决策的时候,只是枚举这一组所有的物品,一个物品都不选的那种决策会通过max比较筛掉,而不需要我们多一种不存在的为0的枚举
for(int k=0;k<=s;k++)
for(int k=1;k<=s;k++)
选不选通过比较决定,只需要枚举该组之中所有的物品

一维写法

#include <iostream>
using namespace std;
const int N=105;
int w[N];//体积
int v[N];//价值
int dp[N];
int main(){int n,m;cin>>n>>m;for(int i=1;i<=n;i++){//前i组件商品 int s;cin>>s;for(int l=1;l<=s;l++){cin>>w[l]>>v[l]; }//输入这组商品的价值 for(int j=m;j>=0;j--){for(int k=1;k<=s;k++){if(j>=w[k])dp[j]=max(dp[j],dp[j-w[k]]+v[k]);}    } } cout<<dp[m];  return 0;
}

混合背包

题目链接

题目们

474. 一和零

题目链接

这里的 一和零是字符呀TT,不要再拿字符串的元素去和整数作比较了
二维背包

int findMaxForm(vector<string>& strs, int m, int n) {//  int w1[N];//限制1,0 的个数,<=m
//  int w2[N];//限制2,1 的个数,<=nint dp[m+1][n+1];for(int i=0;i<=m;i++){for(int j=0;j<=n;j++){dp[i][j]=0;}}// vector<vector<int> > dp(m+1,vector<int> (n+1,0));int w1,w2;int len=strs.size();for(int i=0;i<len;i++){w1=w2=0;int s=strs[i].size();for(int x=0;x<s;x++){if(strs[i][x]=='0')w1++;else if(strs[i][x]=='1')w2++;}for(int j=m;j>=w1;j--){for(int k=n;k>=w2;k--){dp[j][k]=max(dp[j][k],dp[j-w1][k-w2]+1);}}} return dp[m][n];
}

416. Partition Equal Subset Sum(解释恰好装满型)

题目链接

题意:从所有元素中取出若干是否能恰好装满sum/2容量的背包
恰好装满型,零一背包(或者说是 多重背包拆成0-1背包)

一个正整数数组是否可以 划分为两个元素之和相等的子集
哈哈哈哈哈哈哈哈哈哈这题搞懂 背包恰好装满情形

可以回答为什么初始化dp数组为负无穷而不是-1甚至0了

上面提出疑问时就说了,既然得到某个容量背包的价值时首先对该容量的确定,是取若干个商品的组合(取几个商品的体积之和S,对应的容量为S的背包自然可以被装满),当然了,这个取几个商品组合的过程是通过有条件的递推,什么条件呢?就是要从 能被装满的背包的价值 的基础上,遍历后来的商品,取或不取

就是相当于, 状 态 d p [ j ] 由 状 态 d p [ j − n u m [ i − 1 ] ] 状态dp[j]由状态dp[j-num[i-1]] 状态dp[j]由状态dp[j−num[i−1]]推出来,那么 d p [ j − n u m [ i − 1 ] ] dp[j-num[i-1]] dp[j−num[i−1]]这个状态就要是存在的,有意义的
而这里的有意义,指的就是 d p [ j − n u m [ i − 1 ] ] dp[j-num[i-1]] dp[j−num[i−1]]对应的是若干件商品组合而成的能装满的对应容量的背包的价值

这里对dp数组没有意义的值设置为负无穷,是为了在执行状态状态转移方程时,

dp[j]=max(dp[j],dp[j-num[i-1]]+1);

方便判断dp[j-num[i-1]]是否对应的是能被装满的背包,只有先满足这个条件,才有资格去与dp[j]进行大小比较

而对初始没有意义的值初始化为负无穷,就可以借助方程中天然的max函数(本来是判断第二个条件的)来筛掉不满足第一个条件的状态
当然了,不初始化为负无穷而是初始化为-1,也可以,但是就得先单独判断第一个条件(否则的话,max(dp[j],dp[j-nums[i-1]]+1)的比较,谁大谁小就说不定了,因为后者带了个后缀呀)

if(dp[j-nums[i-1]]!=-1)dp[j]=max(dp[j],dp[j-nums[i-1]]+1);

1、初始化为-1

bool canPartition(vector<int>& nums) {vector<int> dp(10005,-1);//dp数组范围是背包最大容量噢,sum/2dp[0]=0;int n=nums.size();int sum=0;for(int i=0;i<n;i++){sum+=nums[i];}if(sum%2!=0)return false;for(int i=1;i<=n;i++){for(int j=sum/2;j>=nums[i-1];j--){//nums数组从0存储,但i必须从1开始遍历,
//因为dp数组,dp【0】代表一件商品都不选而非选第0件 if(dp[j-nums[i-1]]!=-1)dp[j]=max(dp[j],dp[j-nums[i-1]]+1); }}if(dp[sum/2]>0)return true;//>=0也是能过的else return false;
}
if (dp[j - w[i]] != -1 && dp[j - w[i]] + v[i] >= dp[j])

2、初始化为负无穷(max(dp[j],dp[j-nums[i-1]]+1),如果dp[j-nums[i-1]]是不存在的状态,只要dp[j-nums[i-1]]+1的值比0还小,自然可以被dp[j](至少为0)比下去,要想不比较,至少初始化为-2, − 2 + 1 < 0 -2+1<0 −2+1<0

bool canPartition(vector<int>& nums) {vector<int> dp(10005,-2);//求的不是取多少个元素,只是是否可行dp[0]=0;int n=nums.size();int sum=0;for(int i=0;i<n;i++){sum+=nums[i];}if(sum%2!=0)return false;for(int i=1;i<=n;i++){for(int j=sum/2;j>=nums[i-1];j--){//nums数组从0存储,但i必须从1开始遍历,
//因为dp数组,dp【0】代表一件商品都不选而非选第0件 dp[j]=max(dp[j],dp[j-nums[i-1]]+1); }}if(dp[sum/2]>0)return true;else return false;
}

3、初始化为false

//由于 求的不是取多少个元素,只是是否可行

bool canPartition(vector<int>& nums) {vector<int> dp(10005,false);dp[0]=true;int n=nums.size();int sum=0;for(int i=0;i<n;i++){sum+=nums[i];}if(sum%2!=0)return false;//1 2 3 5for(int i=1;i<=n;i++){for(int j=sum/2;j>=nums[i-1];j--){//nums数组从0存储,但i必须从1开始遍历,
//因为dp数组,dp【0】代表一件商品都不选而非选第0件 // dp[j]=max(dp[j],dp[j-nums[i-1]]+1); dp[j]=dp[j]||dp[j-nums[i-1]];}}if(dp[sum/2])return true;else return false;
}

4、bitset优化

bitset相当于bool数组,bool[sum]=1表示能得到各商品总和为sum的组合
用bitset来记录这些商品所有可能组合的和。

具体步骤是: 开辟一个大小为5001的bisets(因为所有元素和不超过10000)名为bits,最后得到的bits满足bits[i]=1则代表nums中某些元素的和为i,最后判断bits[sum/2]是否为1即可

初始时bits[0] = 1,然后从前往后遍历nums数组,对于当前遍历到的数字num,把 bits 向左平移 num 位,然后再或上原来的 bits,这样就代表在原先的基础上又新增了一个和的可能性。 比如对于数组 [1,3],初始化 bits 为 …00001,遍历到1,bits 变为…00011,然后遍历到3,bits 变为了 …11011。最终得到的bit在第1,3,4位上为1,代表了可能的和为1,3,4,这样遍历完整个数组后,去看 bits[sum/2] 是否为1即可。

 00000000000001//初始bit[0]=1 00000000000010//左移1位
--->00000000000011//包含了组合为0,1的可能 00000000001100//左移2位
--->00000000001111//包含了组合为0,1,2,3的可能00000001111000//左移3位
--->00000001111111//包含了组合为0,1,2,3,4,5,6的可能  00111111100000//左移5位
--->00111111111111//包含了0,1,2,3,4,5,6,7,8,9,10,11这所有的可能

其实也很好理解呀,bit=bit|(bit<<nums[i]);
前一个bit保留了上一次得到的所有组合结果,后一个bit<<nums[i],在上一次得到的每个结果的基础上加上nums[i]
nums[i]的所有情况都包含在后者里,不取nums[i]的所有情况都包含在前者中
这实际上是个非常暴力的思想,但是借助位运算和bit非常巧妙的极大优化

bool canPartition(vector<int>& nums) {int n=nums.size();int sum=0;for(int i=0;i<n;i++){sum+=nums[i];}if(sum&1)return false;//sum奇数 sum >>= 1;bitset<10005> bit(1);//所有元素之和不会超过20000,一半就是10000
//      相当于bit[0]=1啦for(int i=0;i<n;i++){bit=bit|(bit<<nums[i]);} if(bit[sum])return true;else return false;
}

322. Coin Change

题目链接

2 31 2^{31} 231数量级也就是 2 e 10 2e10 2e10,脑子不清楚乍一看以为是什么惊天大数字,太差劲了,对于这个数字应该非常熟悉才是,int的数据范围嘛
int的取值范围为: − 2 31 — — 2 31 − 1 -2^{31} ——2^{31}-1 −231——231−1,即-2147483648——2147483647
所以非常值得注意的是溢出风险(爆int), dp[j]=min(dp[j],dp[j-coins[i-1]]+1);
当然了这题没有,amount范围小,虽然是for(int j=coins[i-1];j<=amount;j++)正方向枚举,但是大于amount的coins[i]进不去循环,能进去的不会溢出

题意:每种物品(某种面值的硬币)有无数个,怎样取最少的物品放慢容量为amount的背包,面值看成商品重量,硬币个数看成价值(每个硬币价值为1),要得最少的价值啦
恰好装满型,完全背包

int coinChange(vector<int>& coins, int amount) {const int inf=0x3f3f3f;//要大于最小的,初始化为正无穷大 vector<int> dp(amount+1,inf);dp[0]=0; int cnt=coins.size();for(int i=1;i<=cnt;i++){for(int j=coins[i-1];j<=amount;j++){//完全背包正着枚举噢 dp[j]=min(dp[j],dp[j-coins[i-1]]+1);// if(dp[j-coins[i-1]]+1<dp[j])dp[j]=dp[j-coins[i-1]];// if(dp[j-coins[i-1]]<dp[j]-1)dp[j]=dp[j-coins[i-1]];}} if(dp[amount]==inf)return -1;else return dp[amount];
}

494. Target Sum

494. Target Sum

0-1背包,恰好装满型,不求价值的最值,求取法总和
比较巧妙地转化成了恰好装满背包的问题,主要因为问的是给所有数组元素赋予一些 ′ + ′ , ′ − ′ '+','-' ′+′,′−′之后得到的式子的值是一个准确的值,而不是某个整数k的倍数(如果是这种,那么求得不是取法而是是否能取到)

int findTargetSumWays(vector<int>& nums, int target) {//      正整数序列,带'+'的元素之和为A,带'-'的元素之和为B
//    A+B=sum,A-B=target, A=(sum+target)/2 恰好装满型
//  有几种装法,装满A
//0-1背包,恰好装满型,不求价值的最值,求取法总和 int cnt=nums.size();int sum=0;for(int i=0;i<cnt;i++){sum+=nums[i];} if((sum+target)<0||target>sum)return 0;//有一个样例就是[100],-200 if((sum+target)&1)return 0;int A=(sum+target)>>1;vector<int> dp(1005,0);dp[0]=1;for(int i=1;i<=cnt;i++){for(int j=A;j>=nums[i-1];j--){dp[j]=dp[j]+dp[j-nums[i-1]];//取或不取,两种取法都要 } }return dp[A];
}

hdu4968 最大最小GPA

等平台开了再交上去试试
全网没找到完整的题目,都少了计算GPA的方法TT
结合众题解逆推,
题意:已知n门科目(n<=10)平均分为avg,每门分数都不小于60,求可能的最大和最小GPA。
GPA是四分制,分五个档次来分别计分

60-69 2.0
70-74 2.5
75-79 3.0
80-84 3.5
85-100 4.0

暴力

暴力枚举

#include <iostream>
using namespace std;
#include <math.h>
int main(){int n,avg;cin>>n>>avg;double minx=0x3f3f3f3f;double maxx=0;
//  暴力枚举,n(<=10)个分数,分别处在五个等级里的个数 for(int a=0;a<=n;a++){for(int b=0;b<=n-a;b++){for(int c=0;c<=n-a-b;c++){for(int d=0;d<=n-a-b-c;d++){int e=n-a-b-c-d;
if(60*a+70*b+75*c+80*d+85*e<=n*avg&&69*a+74*b+79*c+84*d+100*e>=n*avg)minx=min(minx,a*2.0+b*2.5+c*3.0+d*3.5+e*4.0);maxx=max(maxx,a*2.0+b*2.5+c*3.0+d*3.5+e*4.0);}}} }cout<<minx<<endl;cout<<maxx<<endl;return 0;
}
//60-69 2.0
//70-74 2.5
//75-79 3.0
//80-84 3.5
//85-100 4.0

分组背包做法

#include <iostream>
using namespace std;
#include <math.h>
#include <vector>
const int N=105;
double v[N];
int w[N];
#define inf 0x3f3f3f3f
void init(){for(int i=60;i<=100;i++){w[i]=i;if(i>=60&&i<=69)v[i]=2.0;else if(i>=70&&i<=74)v[i]=2.5;else if(i>=75&&i<=79)v[i]=3.0;else if(i>=80&&i<=84)v[i]=3.5;else if(i>=85&&i<=100)v[i]=4.0;}
}
int main(){int n,avg;cin>>n>>avg;double m=n*avg*1.0;//n组,每组选一个物品,每组都有60-100对应的这41种决策
//分数是体积,gpa值是价值 vector< vector<double> >dp1(11,vector<double>(m+1,-inf));vector< vector<double> >dp2(11,vector<double>(m+1,inf));dp1[0][0]=0;dp2[0][0]=0;init();for(int i=1;i<=n;i++){for(int j=0;j<=m;j++){//          dp1[i][j]==dp1[i-1][j];
//          dp2[i][j]==dp2[i-1][j];
//这是代表在这一组里不选的情形,显然不行,每一科都要有分数
//每一组都要选一个,即使上一组选得的最大值大于这组任意决策也不行
//必须选这一组的某个决策 for(int k=60;k<=100;k++){if(j>=w[k]){//如果dp1只是初始化为-1,显然不能通过max筛去所有未更新的dp值(不选
//if(dp1[i-1][j-w[k]]!=-1) {//dp1和dp2同时更新,
//所以dp1更新则dp2也必定更新,只有更新过的dp值才有效
//其实除却初始值是无穷大,默认筛掉,都可以明面上做个判断
//if(dp1[i-1][j-w[k]]+v[k]!=dp1[]的初始值) 这样 dp1[i][j]=max(dp1[i][j],dp1[i-1][j-w[k]]+v[k]);dp1[i][j]=min(dp2[i][j],dp2[i-1][j-w[k]]+v[k]);}}} } cout<<dp1[n][m]/n<<" "<<dp2[n][m]/n;//平均绩点 return 0;
}

初始化是个非常迷惑的地方,需要厘清每个变量表示的含义,和状态转移方程中数组下标的范围
比如
i从0枚举
n从0枚举,状态转移方程是

dp1[i+1][j]=max(dp1[i+[j],dp1[i][j-w[k]]+v[k]);

因为要仅仅凭借

 dp1[0][0]=0;dp2[0][0]=0;

这两个初始状态来递推所有的状态,如果i从0枚举,对应的这一组要由dp[i-1]递推而来,但是dp[-1]不存在,dp[0]首先推出的就是dp[1],那i从0遍历,第一组就无法得出结果

i从2枚举
这个更强了,初始化了i为0和1的初始状态,下次就可以直接从dp[2]开始推,即第一个推的就是i=2对应的dp【2】
我自己写的这种,初始化了i=0的,从dp[1]开始推,即第一个推的就是i=1对应的dp【1】,而i也是从1枚举到n,每种决策都能被推出

贪心做法

贪心
https://www.it610.com/article/5625881.htm
添加链接描述
添加链接描述

参考

Acwing y总讲解

动态规划之背包问题系列

0-1背包恰好装满的情形

01背包输出路径、完全背包、多重背包相关推荐

  1. vijos 1071 01背包+输出路径

    描述 过年的时候,大人们最喜欢的活动,就是打牌了.xiaomengxian不会打牌,只好坐在一边看着. 这天,正当一群人打牌打得起劲的时候,突然有人喊道:"这副牌少了几张!"众人一 ...

  2. 多重背包java版本实现_多重背包1(java)

    有 NN 种物品和一个容量是 VV 的背包. 第 ii 种物品最多有 sisi 件,每件体积是 vivi,价值是 wiwi. 求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大. ...

  3. 背包九讲之三(多重背包)

    证明如下: 系数可取值 1,2,4,..2^(k-1),n[i]-2^k+1, k是使得n[i]-2^k+1>=0的最大整数 前n项和为2^k-1,那么最后一项为 n[i]-2^k+1 这些系数 ...

  4. 背包问题(01背包,完全背包,多重背包(朴素算法二进制优化))

    写在前面:我是一只蒟蒻~~~ 今天我们要讲讲动态规划中~~最最最最最~~~~简单~~的背包问题 1. 首先,我们先介绍一下  01背包 大家先看一下这道01背包的问题   题目   有m件物品和一个容 ...

  5. 动态规划总结(01背包 完全背包 多重背包)

    动态规划总结(01背包 完全背包 多重背包) 一.学习资料 1.UVA DP 入门专题 2.夜深人静写算法(二) - 动态规划 3.算法之动态规划 4.什么是动态规划?动态规划的意义是什么? 5.01 ...

  6. 【python】一篇讲透背包问题(01背包 完全背包 多重背包 二维费用背包)

    面对背包问题,有一个很重要的方程式:状态转移方程式 所以每一种背包问题我都会给出状态转移方程式 #01背包 什么是01背包型问题? 先给大家感受一下01背包型问题: 给定n种物品和一背包.物品i的重量 ...

  7. 背包问题(多重背包+0-1背包)

    一:0-1背包问题 #include<iostream> #include<algorithm> #include<cstring> const int maxn= ...

  8. (多重背包+记录路径)Charlie's Change (poj 1787)

    http://poj.org/problem?id=1787   描述 Charlie is a driver of Advanced Cargo Movement, Ltd. Charlie dri ...

  9. 动态规划 4、基础背包问题总结(多重背包与多重背包的转化)

    描述: 有N种物品和一个容量为V的背包.第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大. 变式:有的物品 ...

最新文章

  1. 用Quartus II Timequest Timing Analyzer进行时序分析 :实例讲解 (一)
  2. 谷歌量子计算突破登Science封面!首次对化学反应进行量子模拟
  3. 上交大计算机导师俞凯,WLA青科聊高考①|偶像剧“男主”、上海交大教授俞凯的学霸人生...
  4. 【腾讯Bugly干货分享】动态链接库加载原理及HotFix方案介绍
  5. dede文章调用时过滤调 body里面的style属性和值
  6. 属性页中的ON_UPDATE_COMMAND_UI
  7. 数据结构 - 二叉树
  8. 从代码里提取的测试需求
  9. SUSE12系统安装及LVM设置详解
  10. java标签库jstl-el表达式介绍使用配置手册_JAVA EE 实验报告EL表达式和jstl标签库的使用...
  11. UE3 虚幻编辑器控制台命令
  12. php将权限写入session,PHP由session文件夹权限不够引起的报错
  13. 印花固浆在水性印花中的作用
  14. 中国最好的论坛(未分类版)
  15. 柳州哪里有短视频创业直播基地?柳州市互联网协会为您精选4家
  16. python制作恶搞_Python:恶搞,将你朋友照片做成熊猫人表情包
  17. qq物联网 android sdk,qcloud-iot-sdk-android
  18. 浩辰CAD建筑软件教程之门窗套
  19. 「我的microNome组学分析流程」第1版
  20. 错误处理(一)—— 被呼叫方拒绝接收呼叫。 (异常来自 HRESULT:0x80010001 (RPC_E_CALL_REJECTED))

热门文章

  1. 在学生时代,要有选择性的读书
  2. MFC BLENDFUNCTION 结构体
  3. 【毕业设计】PHP信电系网站建设设计(源代码+论文)
  4. CF 2023/4/2
  5. Jackson:我是最牛掰的 Java JSON 解析器(有点虚)
  6. powershell 安装scoop 包管理
  7. getline的用法
  8. ORACLE PSU AND OPATTCH UPGRADE
  9. 华为智能家居鸿蒙,美的集团首发支持华为鸿蒙 年内推出配套智能家居
  10. QQ自由幻想囧表情引领新时尚!