题单:背包问题 - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
一:01背包
1:原理及模板题
下面代码的注释阐述了原理
/*例题:https://www.luogu.com.cn/problem/P1048
01背包
设F[i][v]表示将前i件物品放入容量为v的背包中时最大收益值
C[i]为第i件物品花费
W[i]为第i件物品的价值
则有F[i][v]=max(F[i-1][v],F[i-1][v-C[i]]+W[i])
即可以取,也可以不取,取的话就从背包容量为v-C[i]到v,因为这件物品重C[i]
i从1~N,v从C[i]~V (小于C[i]放不下)
空间优化:F[v]表示已经装了体积为v的物品时最大收益值,i从1~N,但v从V~C[i],这样的话F[v]由F[v-C[i]]得到
*/
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
const int MAXN=1e6;
int N,V;
int F[MAXN];
int C[MAXN];
int W[MAXN];
void zeroOnePack(int f[],int c,int w){//取一件物品
for(int v=V;v>=c;v--){//不要写成v>=0
f[v]=max(f[v],f[v-c]+w);
}
}
/*使用时可以这么用:
for(int i=1;i<=N;i++)
zeroOnePack(F,C[i],W[i]);
*/
int main(){
scanf("%d%d",&V,&N);
for(int i=1;i<=N;i++){
int c=0,w=0;
scanf("%d%d",&c,&w);
if(c>V){
N--;
i--;
continue;
}
C[i]=c;W[i]=w;
}
for(int i=1;i<=N;i++){
zeroOnePack(F,C[i],W[i]);
}
printf("%d",F[V]);
return 0;
}
2:背包计数
这里解释一下初始化:装满什么都不取的空背包有且仅有一种解决方案;剩下的都是0,比如装满什么都不取的体积为3的背包,解决方案是0
P1164 小A点菜 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<cstdio>//https://www.luogu.com.cn/problem/P1164
#include<iostream>//背包方案计数
#include<algorithm>
#include<math.h>
using namespace std;
const int MAXN=1e6;
int N,V;
int F[MAXN];
int C[MAXN];
int sum(int a,int b){
return a+b;
}
void zeroOnePack(int f[],int c){//取一件物品
for(int v=V;v>=c;v--){//不要写成v>=0
f[v]=sum(f[v],f[v-c]);
}
}
int main(){
F[0]=1;
scanf("%d%d",&N,&V);
for(int i=1;i<=N;i++){
int c=0;
scanf("%d",&c);
if(c>V){
N--;
i--;
continue;
}
C[i]=c;
}
for(int i=1;i<=N;i++){
zeroOnePack(F,C[i]);
}
printf("%d",F[V]);
return 0;
}
3:01变体
P1049 [NOIP2001 普及组] 装箱问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
乍一看这题少了一维,需要重新推公式有点麻烦,其实根据高数思想,少一维度就令那个维度为单位长度即可,也就是说让少的价值维度W[i]单位化。考虑到求箱子剩余空间,我们干脆令物品的价值等于其体积即可,1体积=1价值,W=C。最后求V-F[V]即可
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
const int MAXN=1e6;
int N,V;
int F[MAXN];
int C[MAXN];
int W[MAXN];
void zeroOnePack(int f[],int c,int w){//取一件物品
for(int v=V;v>=c;v--){//不要写成v>=0
f[v]=max(f[v],f[v-c]+w);
}
}
int main(){
scanf("%d%d",&V,&N);
for(int i=1;i<=N;i++){
int c=0;
scanf("%d",&c);
if(c>V){
N--;
i--;
continue;
}
C[i]=W[i]=c;
}
for(int i=1;i<=N;i++){
zeroOnePack(F,C[i],W[i]);
}
printf("%d",V-F[V]);
return 0;
}
P1060 [NOIP2006 普及组] 开心的金明 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这题没啥说的,W[i]=W[i]*C[i]罢了
#include<cstdio>//https://www.luogu.com.cn/problem/P1060
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
const int MAXN=1e6;
int N,V;
int F[MAXN];
int C[MAXN];
int W[MAXN];
void zeroOnePack(int f[],int c,int w){//取一件物品
for(int v=V;v>=c;v--){//不要写成v>=0
f[v]=max(f[v],f[v-c]+w);
}
}
int main(){
scanf("%d%d",&V,&N);
for(int i=1;i<=N;i++){
int c=0,w=0;
scanf("%d%d",&c,&w);
if(c>V){
N--;
i--;
continue;
}
C[i]=c;W[i]=w*c;
}
for(int i=1;i<=N;i++){
zeroOnePack(F,C[i],W[i]);
}
printf("%d",F[V]);
return 0;
}
4 体积压缩
这题恶心在体积太大了,数组开不了这么大,我们需要压缩
Luogu P3985 不开心的金明 - do_while_true - 博客园 (cnblogs.com)
这个大佬写的很好
二 完全背包
1:稍坑模板题
P1616 疯狂的采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这道题的坑在于:
F需要long long,后面printf忘了lld,WA了我好几次没找到原因
//完全背包https://www.luogu.com.cn/problem/P1616
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int read();
const int MAXN=1e7+10;
long long F[MAXN];
int C[MAXN],W[MAXN];
int V,N;
void completePack(long long F[],int C,int W){
for(int v=C;v<=V;v++)//易错:v from C to V instead of 1
F[v]=max(F[v],F[v-C]+W);
}
int main(){
V=read(),N=read();
for(int i=1;i<=N;i++){
int c=read(),w=read();
if(c>V){
i--,N--;
continue;
}
C[i]=c,W[i]=w;
}
for(int i=1;i<=N;i++)
completePack(F,C[i],W[i]);
printf("%lld",F[V]);
return 0;
}
int read(){
char c;
int s=0;
int w=1;
while(c<'0' || c>'9'){
if(c=='-') w*=-1;
c=getchar();
}
while(c>='0' && c<='9'){
s=c-'0'+(s<<3)+(s<<1);
c=getchar();
}
return w==1?s:-s;
}
P2722就纯小白模板了,略
2:体积逐次递增的完全背包
P1853 投资的最大效益 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这道题中,总体积每年都会增加,有两种思路,一种是每年都memset,一种是开二维数组,都可以,一定要注意除以1000时函数的细节,我被细节坑了好久,下面分别给出两种思路的AC代码
i memset版
//完全背包https://www.luogu.com.cn/problem/P1616
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int read();
const int MAXN=5e4+10;
int F[MAXN];
int C[10010],W[1010];
int V,N,Year;
void completePack(int F[],int C,int W){
for(int v=C;v<=V/1000;v++){//易错:v from C to V instead of 1
F[v]=max(F[v],F[v-C]+W);
}
}
int main(){
V=read(),Year=read(),N=read();
for(int i=1;i<=N;i++){
int c=read(),w=read();
C[i]=c,W[i]=w;
}
for(int y=1;y<=Year;y++){
memset(F,0,sizeof(F));
for(int i=1;i<=N;i++){
completePack(F,C[i]/1000,W[i]);
}
V+=F[V/1000];
}
printf("%d",V);
return 0;
}
int read(){
char c;
int s=0;
int w=1;
while(c<'0' || c>'9'){
if(c=='-') w*=-1;
c=getchar();
}
while(c>='0' && c<='9'){
s=c-'0'+(s<<3)+(s<<1);
c=getchar();
}
return w==1?s:-s;
}
ii 二维数组版
//完全背包https://www.luogu.com.cn/problem/P1853
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int read();
const int MAXN=5e4+10;
int F[41][MAXN];
int C[10010],W[1010];
int V,N,Year;
void completePack(int Y,int C,int W){
for(int v=C;v<=V/1000;v++){//易错:v from C to V instead of 1
F[Y][v]=max(F[Y][v],F[Y][v-C]+W);
}
}
int main(){
V=read(),Year=read(),N=read();
for(int i=1;i<=N;i++){
int c=read(),w=read();
C[i]=c,W[i]=w;
}
for(int y=1;y<=Year;y++){
for(int i=1;i<=N;i++){
completePack(y,C[i]/1000,W[i]);
}
V+=F[y][V/1000];
}
printf("%d",V);
return 0;
}
int read(){
char c;
int s=0;
int w=1;
while(c<'0' || c>'9'){
if(c=='-') w*=-1;
c=getchar();
}
while(c>='0' && c<='9'){
s=c-'0'+(s<<3)+(s<<1);
c=getchar();
}
return w==1?s:-s;
}
3 求最小价值的完全背包
P2918 [USACO08NOV]Buying Hay S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这题把H当成V,P当成C,C当成W就转化成求最小价值的完全背包了
很简单,把状态转移方程的MAX转为MIN即可
#include<cstdio>
#include<iostream>
using namespace std;
#define foreach(N) for(int i=1;i<=(N);i++)
int V,N;
const int MAXN=1e7+10;
int F[MAXN],C[MAXN],W[MAXN];
void completePack(int F[],int C,int W){
for(int i=C;i<=V;i++) F[i]=min(F[i],F[i-C]+W);
}
int read(){
int s=0,w=1;
char c=getchar();
while(c<'0' || c>'9'){
if(w=='-') w*=-1;
c=getchar();
}
while(c>='0' && c<='9'){
s=c-'0'+(s<<3)+(s<<1);
c=getchar();
}
return s*w;
}
int main(){
N=read(),V=read();
foreach(N){
int c=read(),w=read();
if(c>V){
i--,N--;
continue;
}
C[i]=c,W[i]=w;
}
foreach(V) F[i]=0x3f3f3f3f;
foreach(N){
completePack(F,C[i],W[i]);
}
printf("%d",F[V]);
return 0;
}
三 多重背包
1:混合背包转为多重背包
P1833 樱花 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
混合背包,不过直接转多重背包处理也一样
多重背包转二进制优化原理是把共M件物品拆分为K件,这K件选与不选的所有组合等价于选0~M件,最终都是转为01背包。
#include<cstdio>
#include<iostream>
using namespace std;
#define foreach(N) for(int i=1;i<=(N);i++)
int V,N;
const int MAXN=1e7+10;
int F[MAXN],C[MAXN],W[MAXN],M[MAXN];
void completePack(int F[],int C,int W){
for(int i=C;i<=V;i++) F[i]=max(F[i],F[i-C]+W);
}
void zeroOnePack(int F[],int C,int W){
for(int i=V;i>=C;i--) F[i]=max(F[i],F[i-C]+W);
}
void multiplePack(int F[],int C,int W,int M){
if(C*M>V){
completePack(F,C,W);
foreach(N)
return;
}
int k=1;
while(k<M){
zeroOnePack(F,C*k,W*k);
M-=k;
k*=2;
}
zeroOnePack(F,C*M,W*M);
}
int read(){
int s=0,w=1;
char c=getchar();
while(c<'0' || c>'9'){
if(w=='-') w*=-1;
c=getchar();
}
while(c>='0' && c<='9'){
s=c-'0'+(s<<3)+(s<<1);
c=getchar();
}
return s*w;
}
int main(){
int a=read(),b=read(),c=read(),d=read();
V=(c-a-1)*60+60-b+d;
N=read();
foreach(N){
int c=read(),w=read(),m=read();
if(c>V){
i--;N--;
continue;
}
C[i]=c,W[i]=w;
if(!m) M[i]=1100;
else M[i]=m;
}
foreach(N) multiplePack(F,C[i],W[i],M[i]);
printf("%d",F[V]);
return 0;
}
2 求乘积的多重背包
P5365 [SNOI2017]英雄联盟 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
很有意思,遇到变式,我们第一件事就是先写状态转移方程(滚动处理前)
原方程是F[i][j]=max (F[i-1][j],F[i-1][j-C]+W)
而现在我们要求的是乘积
容易想到F[i][j]=max (F[i-1][j],F[i-1][j-C]*W)
构架有了,接下来就是带入参数了
显然W的数学意义是选取的件数,即W=1~K[i]
那么C就是选W件所花的钱,即C=C[i]*W=C[i]*K[i]
即F[i][j]=max (F[i-1][j],F[i-1][j-C*K]*K)
F[0]=1
本题务必注意long long
#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN=1e6+10;
#define ll long long
#define foreach(N) for(int i=1;i<=(N);i++)
ll F[MAXN],M;
int C[1000],K[1000];
int V,N;
int read(){
int s=0;
char c=getchar();
while(c<'0' || c>'9')
c=getchar();
while(c>='0' && c<='9'){
s=(s<<3)+(s<<1)+c-'0';
c=getchar();
}
return s;
}
ll readll(){
ll s=0;
char c=getchar();
while(c<'0' || c>'9')
c=getchar();
while(c>='0' && c<='9'){
s=(s<<3)+(s<<1)+c-'0';
c=getchar();
}
return s;
}
void MultiplePack(ll F[],int C,int K){
for(int i=V;i>=C;i--)
for(ll j=1;j<=K && i-j*C>=0;j++)
F[i]=max(F[i],F[i-j*C]*j);
}
int main(){
F[0]=1;
N=read();M=readll();
foreach(N) K[i]=read();
foreach(N) C[i]=read();
foreach(N) V+=K[i]*C[i];
foreach(N) MultiplePack(F,C[i],K[i]);
foreach(V)
if(F[i]>=M) {
printf("%d",i);
break;
}
return 0;
}
3 求总方案数的多重背包
模板是 F[i][j]=sum F[i-1][j]+F[i-1][j-C]
P1077 [NOIP2012 普及组] 摆花 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
先求状态转移方程:设F[i][j]表示摆放第i种花,一共摆了j盆的方案总数
k从1到a[i],表示第i种花摆几盆
F[i][j]=sum(F[i-1][j],F[i][j-k])%p
滚动数组降低空间复杂度
F[j]=sum(F[j],F[j-k])%p
j从M到K
K从1到a
注意循环次序不能调换,先循环j再循环k,否则会造成在摆a[l]盆的基础上求摆a[l+1]盆的方案数,显然错误
函数调用时循环i从1到n
#include<cstdio>
int F[110],A[110];
int N,M;
const int p=1e6+7;
#define sum(a,b) ((a)+(b))
#define foreach(N) for(int i=1;i<=(N);i++)
void MultiplePack(int F[],int A){
for(int j=M;j>=0;j--){
for(int k=1;k<=A && j-k>=0;k++)
F[j]=sum(F[j],F[j-k])%p;
}
}
int read(){
int s=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9'){
s=(s<<3)+(s<<1)+c-'0';
c=getchar();
}
return s;
}
int main(){
F[0]=1;
N=read(),M=read();
foreach(N) A[i]=read();
foreach(N) MultiplePack(F,A[i]);
printf("%d",F[M]);
return 0;
}
4:求可行性的多重背包
P2347 [NOIP1996 提高组] 砝码称重 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
有一个很有意思的解法
bitset解法
#include<cstdio>
#include<bitset>
using namespace std;
int W[]={1,2,3,5,10,20};
bitset<1001> S;
int main(){
S[0]=1;
for(int i=0;i<6;i++){
int c;
scanf("%d",&c);
for(int j=0;j<c;j++){
S|=S<<W[i];
}
}
printf("Total=%d",S.count()-1);
}
很有意思
多重背包解法
这跟上一题差别其实不大,只要对多重背包理解没问题按部就班也能写出来
#include<cstdio>//https://www.luogu.com.cn/problem/P2347
#include<iostream>
using namespace std;
#define sum(a,b) ((a)+(b))
int F[1010],M[7],N,V;
void ZeroOnePack(int F[],int C){
for(int i=V;i>=C;i--) F[i]=sum(F[i],F[i-C]);
}
void CompletePack(int F[],int C){
for(int i=C;i<=V;i++) F[i]=sum(F[i],F[i-C]);
}
void MultiplePack(int F[],int C,int M){
if(C*M>V){
CompletePack(F,C);
return;
}
int K=1;
while(K<M){
ZeroOnePack(F,C*K);
M-=K;
K*=2;
}
ZeroOnePack(F,C*M);
}
int main(){
F[0]=1;
int C[7]={0,1,2,3,5,10,20};
for(int i=1;i<=6;i++){
scanf("%d",M+i);
V+=M[i]*C[i];
}
for(int i=1;i<=6;i++) MultiplePack(F,C[i],M[i]);
for(int i=1;i<=V;i++) if(F[i]) N++;
printf("Total=%d",N);
}
还有一种思路供大家欣赏