DP—背包问题
基本0-1背包问题
题设:有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大
f[i][j]=max( f[i-1][j] , f[i-1][j-w[i]]+v[i]) //f[i][j]表示前i件作品占容量j时的最大价值;注意这个占的容量并不一定占满
第i件物品是否要放入容量j的背包中取决于是原有最好的的物品放入价值高还是在加入这个物品价值高;
常规解法:
#include<iostream>
#include<stdio.h>
#include<string.h>
const int len_A=1e5;
using namespace std;
int find_A(int m,int n);
int ans_A[len_A];
int w_A[len_A],c_A[len_A];
int main(){
int n,v;
while(cin>>n>>v){
for(int i=1;i<=n;i++){
cin>>c_A[i]>>w_A[i];
}
cout<<find_A(n,v)<<endl;
}
return 0;
}
int find_A(int n,int v){
memset(ans_A,0,sizeof ans_A);
for(int i=1;i<=n;i++){
for(int j=v;j>0;j--){
if(c_A[i]<=j)
ans_A[j]=max(ans_A[j],ans_A[j-c_A[i]]+w_A[i]);
}
}
return ans_A[v];
}
解二:
#include <iostream>
#include<stdio.h>
using namespace std;
#define MAX(m,n) (((m)>(n))?(m):(n))
int n,v;
int V[501][30001];//设置背包时其实可以不要i值,因为每次循环的i值都是固定的
void value(int goods[][2]);
int main(){
while((scanf("%d %d",&n,&v))!=EOF){
int goods[n][2];//{weight,value}
for(int i=0;i<n;i++){
scanf("%d %d",&goods[i][0],&goods[i][1]);
}
value(goods);
int result=V[n][v];
printf("%d\n",result);
}
return 0;
}
void value(int goods[][2]){
for(int i=0;i<=n;i++){
V[0][i]=0;//若要求恰好装满背包,应该在初始化时除了f[0][0]之外所有f[0][i]都设为负无穷;
}
for(int i=1;i<=n;i++){
for(int w=0;w<=v;w++){
if(w<goods[i-1][0]){
V[i][w]=V[i-1][w];
}
else{
V[i][w]=MAX(V[i-1][w],V[i-1][w-goods[i-1][0]]+goods[i-1][1]);
}
}
}
}
完全背包问题
题设:每件物品都数量无限
加一个循环每件物品
#include<iostream>
#include<stdio.h>
#include<string.h>
const int len_B=1e5;
using namespace std;
int find_B(int m,int n);
int ans_B[len_B];
int w_B[len_B],c_B[len_B];
int main(){
int n,v;
while(cin>>n>>v){
for(int i=1;i<=n;i++){
cin>>c_B[i]>>w_B[i];//费用与价值
}
cout<<find_B(n,v)<<endl;
}
return 0;
}
int find_B(int n,int v){
memset(ans_B,0,sizeof ans_B);
for(int i=1;i<n;i++){//一个优化排序过程,每件物品与其后物品作比较看价值高质量小者可以直接替换掉比它差者
for(int j=i+1;j<=n;j++){
if(w_B[i]<=w_B[j]&&c_B[i]>=c_B[j]){
w_B[i]=w_B[n],c_B[i]=c_B[n]; //被替换者放到最后去掉
n--,i--;
break;
}
else if(w_B[i]>=w_B[j]&&c_B[i]<=c_B[j]){
w_B[j]=w_B[n],c_B[j]=c_B[n];
n--,j--;
}
}
}
for(int i=1;i<=n;i++){ //选择物品的过程,空间优化只用了一位数组,第i件物品选到容量满
for(int j=0;j<=v;j++){ //这里为什么把初始值j从v改成0就可以了?当从前往后时,第i件物品想用几次就用几次
if(c_B[i]<=j)
ans_B[j]=max(ans_B[j],ans_B[j-c_B[i]]+w_B[i]);
}
}
return ans_B[v];
}
多重背包问题
题设:第i种物品最多有m[i]件可用
#include<iostream>
#include<stdio.h>
#include<string.h>
const int len_C=1e5;
using namespace std;
int find_C(int m,int v);
void Completepack_C(int c,int w,int v);
void ZeroOnepack_C(int c,int w,int v);
void Multiplepack_C(int c,int w,int amount,int v);
int ans_C[len_C];
int w_C[len_C],c_C[len_C],m[len_C];
int main(){
int n,v;
while(cin>>n>>v){
for(int i=1;i<=n;i++){
cin>>c_C[i]>>w_C[i]>>m[i];
}
cout<<find_C(n,v)<<endl;
}
return 0;
}
int find_C(int n,int v){
memset(ans_C,0,sizeof ans_C);
for(int i=1;i<=n;i++){
Multiplepack_C(c_C[i],w_C[i],m[i],v);
}
return ans_C[v];
}
void Multiplepack_C(int c,int w,int amount,int v){
if(c*amount>=v){//当i的数量大于背包容量时相当于一个完全背包
Completepack_C(c,w,v);
return;
}
for(int k=1;k<amount;){//当i的数量有限时,用01a背包法k个k个放
ZeroOnepack_C(k*c,k*w,v);
amount-=k;
k=k<<1;//k乘2;为什么这么设计?节省时间,1,2,4...2^k−1,可以组成小于amount的所有搭配,在一次次01背包中不断选择最优搭配;
}
ZeroOnepack_C(amount*c,amount*w,v);
}
void ZeroOnepack_C(int c,int w,int v){//01背包,
for(int j=v;j>0;j--){
if(c<=j)
ans_C[j]=max(ans_C[j],ans_C[j-c]+w);
}
}
void Completepack_C(int c,int w,int v){//完全背包
for(int j=0;j<=v;j++){
if(c<=j)
ans_C[j]=max(ans_C[j],ans_C[j-c]+w);
}
}
组合背包问题
题设:01背包与完全背包与多重背包的混合(和多重背包差不多)
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int len_D=1e5;
int find_D(int m,int v);
void Completepack_D(int c,int w,int v);
void ZeroOnepack_D(int c,int w,int v);
void Multiplepack_D(int c,int w,int amount,int v);
int ans_D[len_D];
int w_D[len_D],c_D[len_D],m[len_D];
int main(){
int n,v;
while(cin>>n>>v){
for(int i=1;i<=n;i++){
cin>>c_D[i]>>w_D[i]>>m[i];
}
cout<<find_D(n,v)<<endl;
}
return 0;
}
int find_D(int n,int v){
memset(ans_D,0,sizeof ans_D);
for(int i=1;i<=n;i++){
if(m[i]==233)
Completepack_D(c_D[i],w_D[i],v);/
else
Multiplepack_D(c_D[i],w_D[i],m[i],v);
}
return ans_D[v];
}
void Multiplepack_D(int c,int w,int amount,int v){
if(c*amount>=v){
Completepack_D(c,w,v);
return;
}
for(int k=1;k<amount;){
ZeroOnepack_D(k*c,k*w,v);
amount-=k;
k=k<<1;
}
ZeroOnepack_D(amount*c,amount*w,v);
}
void ZeroOnepack_D(int c,int w,int v){
for(int j=v;j>0;j--){
if(c<=j)
ans_D[j]=max(ans_D[j],ans_D[j-c]+w);
}
}
void Completepack_D(int c,int w,int v){
for(int j=0;j<=v;j++){
if(c<=j)
ans_D[j]=max(ans_D[j],ans_D[j-c]+w);
}
}
二维背包问题
题设:选择每件物品必须同时付出两种代价;对于每种代价都有一个可付出的最大值(背包容量)第i件物品所需的两种代价分别为w[i]和g[i] 两种代价可付出的最大值(两种背包容量)分别为V和T。物品的价值为v[i]。
这种背包有多种形式,总的来说就是再加一个条件,多一个维度;
核心:f[i][j][k]=max(f[i−1][j][k],f[i−1][j−w[i]][k−g[i]]+v[i]);
for (int i = 1; i <= n; i++)
for (int j = V; j >= w[i]; j--)
for (int k = T; k >= g[i]; k--)
f[j][k] = max(f[j][k], f[j - w[i]][k - g[i]] + v[i]);
分组背包问题
题设:物品被划分为若干组,每组中的物品互相冲突,最多选一件
核心:f[k][j]=max(f[k−1][j],f[k−1][j−c[i]]+w[i]) //k表示组,i省去
for (所有的组k)
for (int j = V; j >= 0; j--)
for (所有属于组k的i)
f[j] = max{f[j], f[j - w[i]] + v[i]}
链接:https://blog.youkuaiyun.com/yandaoqiusheng/article/details/84782655