一.引入:
装箱问题(洛谷P1049)
题目描述 有一个箱子容量为V(正整数,0≤V≤20000),同时有n个物品(0<n≤30),每个物品有一个体积(正整数)。
要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
#include<iostream>
using namespace std;
int v,n,p[35],i,j,f[20005]={0};
int main()
{
cin>>v;
cin>>n;
for(i=1;i<=n;i++)
{
cin>>p[i];
}
for(i=1;i<=n;i++)
{
for(j=v;j>=p[i];j--)
{
if(f[j-p[i]]+p[i]>f[j])
{
f[j]=f[j-p[i]]+p[i];
}
}
}
cout<<v-f[v]<<endl;
return 0;
}
二、01背包:
采药(洛谷1048)
山洞里有M株不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。在规定的时间T内,采到的草药的总价值最大。
#include<iostream>
using namespace std;
const int N=1010,M=110;
int t,m,f[N],v[M],w[M];
int main()
{
cin>>t>>m;
for(int i=1;i<=m;i++)
{
cin>>v[i]>>w[i];//采摘草药的时间,草药价值。
}
//模板:就地滚动
for(int i=1;i<=m;i++)
{
for(int j=t;j>=v[i];j--)//运用上一层状态:从大到小枚举
{
//f[i][j]=f[i-1][j]
//f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[t]<<endl;
return 0;
}
三.完全背包:
疯狂的采草药(洛谷P1616)
此题与采药问题有所不同:
1.每种草药可以无限制地疯狂采摘。
2.草药的数目多,采药时间很长。
//每种草药数量没有限制
//总时间t(1<=t<=10^7) ,草药种类m(1<=m<=10^4)
//1<=c[i],w[i]<=10^4,m*t<10^7
#include<iostream>
using namespace std;
const int N=10000010,M=10010;
int t,m,f[N],c[M],w[M];
int main()
{
cin>>t>>m;//时间t,数目m。
for(int i=1;i<=m;i++)
{
cin>>c[i]>>w[i];
}
//模板
//注意:第二个for循环与01背包模板循环方向相反
for(int i=1;i<=m;i++)
{
for(int j=c[i];j<=t;j++)//运用本层状态:从小到大枚举
{
//f[i][j]=f[i-1][j];
//f[i][j]=max(f[i][j],f[i][j-c[i]]+w[i]);
f[j]=max(f[j],f[j-c[i]]+w[i]);
}
}
cout<<f[t]<<endl;
return 0;
}
四.多重背包:
样例一:
樱花(洛谷P1833)
有n颗樱花树,每颗树都有美学值Ci,求解在规定时间内看哪几棵樱花树能使美学值最。不同的樱花树能被看的次数有要求,有1到无数次。
//s1现在时间的小时,s2现在时间的分钟,同理e1,e2.z可以用来赏樱花的总时间
//n樱花树的总棵数,f[i]状态转移方程,t[i]看数耗时,c[i]美学值,p[i]次数
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int s1,s2,e1,e2,z,n,f[1005],t[10005],c[10005],p[10005],i,j,k,T[99999],C[99999];
int main()
{
scanf("%d:%d%d:%d%d",&s1,&s2,&e1,&e2,&n);//计算z
if(e2<s1)
{
e1-=1;
e2+=60;
}
z=(e1-s1)*60+(e2-s2);
for(i=1;i<=n;i++)//输入数据,并处理可看无数次的树
{
scanf("%d%d%d",&t[i],&c[i],&p[i]);
if(p[i]==0)//0代表无数次
{
p[i]=9999;//用一个比较大的值表示无数次
}
}
j=1;
for(i=1;i<=n;i++)//转化为01背包:将树分解为多颗有规律的树来解决无限次可看树的问题
{
k=1;
while(p[i])
{
T[j]=k*t[i];
C[j]=k*c[i];
p[i]-=k;
k=k*2;//系数为1,2,4...2^(k-1),且k是满足p[i]-2^k+1>0的最大整数
j++;
if(p[i]<k)
{
T[j]=p[i]*t[i];
C[j]=p[i]*c[i];
p[i]=0;
j++;
}
}
}
n=j;
for(i=1;i<=n;i++)//01背包模板
{
for(j=z;j>=T[i];j--)
{
f[j]=max(f[j],f[j-T[i]]+C[i]);
}
}
printf("%d\n",f[z]);
return 0;
}
样例二:(与样例一有稍微不同)
AcWing 5. 多重背包问题 II
有 N 种物品和一个容量是 V 的背包。第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。
#include<iostream>
using namespace std;
const int N=15000,M=2010;
int n,m,v[N],w[N];
int f[M];
int main()
{
scanf("%d%d",&n,&m);
int cnt=0;
for(int i=1;i<=n;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
//将数量分解 ->新的物品
//1,2,4...2^k,c( c<2^(k+1) );
int k=1;
while(k<=c)
{
cnt++;
v[cnt]=a*k;
w[cnt]=b*k;
c-=k;
k*=2;
}
if(c>0)
{
cnt++;
v[cnt]=a*c;
w[cnt]=b*c;
}
}
n=cnt;//注意
//进行01背包操作
for(int i=1;i<=n;i++)
{
for(int j=m;j>=v[i];j--)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
printf("%d\n",f[m]);
return 0;
}
五.二维费用的背包问题:
NASA的食物计划(洛谷P1507)
有两个约束条件:体积V(<400)和质量M(<400)
在这两个约束条件下,输出能达到的食品方案所含卡路里的最大值
解决方法:在01背包的基础上再加一维
//V<=400,M<=400,N<50
//tj存体积,zl存质量,w存所含卡路里
#include<iostream>
using namespace std;
int v,m,n,tj[405],zl[405],w[405],i,j,k,f[405][405]={0};
int main()
{
cin>>v>>m;
cin>>n;
for(i=1;i<=n;i++)
{
cin>>tj[i]>>zl[i]>>w[i];
}
for(i=1;i<=n;i++)//枚举物品
{
for(j=v;j>=tj[i];j--)//枚举体积
{
for(k=m;k>=zl[i];k--)//枚举质量
{
if(f[j-tj[i]][k-zl[i]]+w[i]>f[j][k])
{
f[j][k]=f[j-tj[i]][k-zl[i]]+w[i];
}
}
}
}
cout<<f[v][m]<<endl;
return 0;
}
注:当发现由熟悉的动态规划题目添加限制条件变形得来的题目时,可以尝试在原来的状态中,加一维以满足新的限制条件。
六.分组背包:
通天之分组背包(洛谷P1757)
不同于 01 背包,物品大致可分为 k 组,每组中的物品相互冲突,求最大的利用价值是多少。
//01背包+物品分为k组,每组物品相冲突
//n件物品,总重量为m,zl质量,jz价值,zs所属组数,bh编号,x组号
#include<iostream>
#include<algorithm>
using namespace std;
int n,m,zl[1005],jz[1005],x,zs[1005]={0},f[1005],bh[1005][1005],i,j,k,t;
int main()
{
cin>>m>>n;
for(i=1;i<=n;i++)
{
cin>>zl[i]>>jz[i]>>x;
t=max(t,x);//记录总组数
zs[x]++;//记录该物品在该组的编号,并记录该组的物品总数
bh[x][zs[x]]=i;//记录输入该物品的输入顺序
}
for(i=1;i<=t;i++)//遍历所有的组
{
for(j=m;j>=0;j--)//从总总量到0变量,放物品
{
for(k=1;k<=zs[i];k++)//判断该组中的每个物品
{
if(j>=zl[bh[i][k]])//要注意的一步,记得要判断放不放的下
f[j]=max(f[j],f[j-zl[bh[i][k]]]+jz[bh[i][k]]);
}
}
}
cout<<f[m]<<endl;
return 0;
}
七.扩展:
1.N<=20,V<=10^9
直接搜(2^20)
2.N<=40,V<=10^9
整体二分+暴力枚举
注:如有错误,欢迎指出