01背包(每个物品只能取一次)
01背包和完全背包优化区别在于01是倒序,完全是正序
/*我们每次计算dp[j] (即dp[i][j]) 的时候都会需要dp[j-w[i]]
(即dp[i-1][j-w[i]])的值。
因为j-w[i]比j小,所以如果我们正序计算,(正序用完全,因为完全可以重复,不需要考虑更新)
那么dp[j-w[i]]就已经更新了 (即dp[i][j-w[i]]),与状态转移方程不符。
#include<bits/stdc++.h>
using namespace std;
int n,m,v[1001],w[1001],f[1001],g[1001];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d%d",&v[i],&w[i]);
}
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]);
}
完全背包(每个物品可取多次)
#include<bits/stdc++.h>
using namespace std;
int n,m,v[1001],w[1001],f[1001],g[1001];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d%d",&v[i],&w[i]);
}
for(int i=1;i<=n;i++){
for(int j=v[i];j<=m;j++){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
printf("%d\n",f[m]);
system("pause");
}
多重背包(规定一个物品可以取多次,转换成01背包问题)
(数学方法:1,2,...,2^m选一些数字相加,可以得出[0,2^(m+1))
#include<bits/stdc++.h>
using namespace std;
int n,m,v[2001],w[2001],f[2001],l[2001];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d%d%d",&v[i],&w[i],&l[i]);
}
for(int i=1;i<=n;i++){
int res=l[i];
for(int k=1;k<=res;res-=k,k*=2)//切成l份
for(int j=m;j>=v[i]*k;j--){
f[j]=max(f[j],f[j-v[i]*k]+w[i]*k);
}
for(int j=m;j>=v[i]*res;j--)//多出来的,好比8=1+2+4+1,多出来一个1
f[j]=max(f[j],f[j-v[i]*res]+w[i]*res);
}
printf("%d\n",f[m]);
system("pause");
}
利用单调队列思想
#include<bits/stdc++.h>
using namespace std;
int n,m,v,w,t,f[10001],c[10001][2];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i <= n; i++){
scanf("%d%d%d",&v,&w,&t);
for(int j=0; j < v; j++){
int k=0,l=1;
for(int p=j,x=1; p <= m;p+=v,++x){
int e=f[p]-x*w,r=x+t;
for(; k >= l && c[k][0] <= e;--k);
c[++k][0]=e;c[k][1]=r;
f[p]=c[l][0]+w*x;
for(; k >= l && c[l][1] == x; ++l);
}
}
}
printf("%d\n",f[m]);
system("pause");
}
分组背包(每个物品属于哪个组,且一个组只能拿一个)
#include<bits/stdc++.h>
using namespace std;
int n,m,v[1001],w[1001],a[1001],f[1001];
vector<int> c[1001];
int main(){
scanf("%d%d",&n,&m);
for(int i=1; i <= n; i++){
scanf("%d%d%d",&a[i],&v[i],&w[i]);
c[a[i]].push_back(i);
}
for(int i=1; i <= 1000; i++)
for(int j=m; j > 0; j--){
for(auto k:c[i])//k是一个下标,枚举选这组里的几种选择
if(v[k]<=j)
f[j]=max(f[j],f[j-v[k]]+w[k]);
}
printf("%d\n",f[m]);
system("pause");
}
二维背包(多了个体力这个限制条件(不一定是体力))
#include<bits/stdc++.h>
using namespace std;
int n,m,v[1001],w[1001],t[1001],f[101][101],k;
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1; i <= n; i++)
scanf("%d%d%d",&v[i],&w[i],&t[i]);
for(int i=1; i<=n; i++)
for(int j=m; j >= v[i]; j--)
for(int x=k; x >= t[i]; x--){
f[j][x]=max(f[j][x],f[j-v[i]][x-t[i]]+w[i]);
}
printf("%d\n",f[m][k]);
system("pause");
}
混合背包(就是01,完全,多重混在一起),由于多重的单调队列思想方法不是很好写,这边就用数学结论的方法
#include<bits/stdc++.h>
using namespace std;
int n,m,k[10001],v[10001],w[10001],f[10001],l[10001];
int main(){
scanf("%d%d",&n,&m);
for(int i=1; i <= n ;i++){
cin>>k[i]>>v[i]>>w[i];
if(k[i]==3){
cin>>l[i];
}
}
for(int i=1;i<=n;i++)
{
if(k[i]==2)//完全背包
{
for(int j=v[i];j<=m;j++){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
if(k[i]==1){
for(int j=m;j>=v[i];j--){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
else//多重背包和01
{
int res=l[i];
for(int p=1;p<=res;res-=p,p*=2)//切成l份
for(int j=m;j>=v[i]*p;j--){
f[j]=max(f[j],f[j-v[i]*p]+w[i]*p);
}
for(int j=m;j>=v[i]*res;j--)//多出来的,好比8=1+2+4+1,多出来一个1
f[j]=max(f[j],f[j-v[i]*res]+w[i]*res);
}
}
cout<<f[m]<<"\n";
system("pause");
}