一個不錯的《揹包九講》的賞析博客。https://blog.csdn.net/yandaoqiusheng/article/details/84782655#commentBox
一、01揹包
https://vjudge.net/problem/HihoCoder-1038
題目
且說上一週的故事裏,小Hi和小Ho費勁心思終於拿到了茫茫多的獎券!而現在,終於到了小Ho領取獎勵的時刻了!
小Ho現在手上有M張獎券,而獎品區有N件獎品,分別標號爲1到N,其中第i件獎品需要need(i)張獎券進行兌換,同時也只能兌換一次,爲了使得辛苦得到的獎券不白白浪費,小Ho給每件獎品都評了分,其中第i件獎品的評分值爲value(i),表示他對這件獎品的喜好值。現在他想知道,憑藉他手上的這些獎券,可以換到哪些獎品,使得這些獎品的喜好值之和能夠最大。
分析
抽象:
揹包容量 m 1e5
石頭個數 n 500
石頭的重量爲 need(i) 2e5
價值爲 value(i) 1e3
設dp[i][j]表示第i個石頭(前i個石頭)、揹包空間剩餘j時可達到的最大收益。
有方程:
dp[i,j]= max( 放:dp[i-1,j-w[i]] + v[i] (條件爲wi<=j),
不放:dp[i-1,j]
);
1<=i<=n
1<=j<=m
觀察方程,與i無關,且每個方程需要得知對應j較小的答案。故空間優化:
dp[j]= max( 放:dp[j-w[i]] + v[i] (條件爲wi<=j),
不放:dp[j]
);
1<=i<=n
m>=j>=1 (倒序)
ac代碼
#include <iostream>
#include <stdlib.h>
using namespace std;
int const maxn=1e5+10;
#define max(i,j) ((i)>(j)?(i):(j))
int v[maxn];
int w[maxn];
int d[maxn];
/*
揹包容量 m 1e5
石頭個數 n 500
石頭的重量爲 need(i) 2e5
價值爲 value(i) 1e3
*/
int main(int argc, const char * argv[]) {
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
scanf("%d%d",w+i,v+i);
}
for(int i=1;i<=n;i++){
for(int j=m;j>=1 && w[i]<=j;j--){
d[j]=max(d[j-w[i]]+v[i],d[j]);
}
}
cout<<d[m]<<endl;
return 0;
}
二、多重揹包
題目
A - 悼念512汶川大地震遇難同胞――珍惜現在,感恩生活
急!災區的食物依然短缺!
爲了挽救災區同胞的生命,心繫災區同胞的你準備自己採購一些糧食支援災區,現在假設你一共有資金n元,而市場有m種大米,每種大米都是袋裝產品,其價格不等,並且只能整袋購買。
請問:你用有限的資金最多能採購多少公斤糧食呢?
分析
混合揹包(多重揹包問題),可直接轉化爲01揹包問題,(也可用二進制優化)。
當然,還有O(vn)的方法,需要用到單調隊列。具體見揹包九講。此處不列舉了。
#include <iostream>
#include <stdlib.h>
#include <cstring>
using namespace std;
int const maxn=1e5+10;
#define max(i,j) ((i)>(j)?(i):(j))
int v[maxn];
int w[maxn];
int dp[200];
int c[maxn];
/*
揹包容量 n 100
種類個數 m 100
石頭個數cnt 2000
重量w 20 價值v 200 個數c 20
*/
int cnt;
inline void func(int x,int y,int z){
int wei=1;
while(z){
if(wei>z){
wei=z;
}
w[++cnt]=wei*x;
v[cnt]=wei*y;
z-=wei;
wei=wei<<1;
}
}
int main(int argc, const char * argv[]) {
int n,m;
int T;
cin>>T;
int x,y,z;
while(T--){
cnt=0;
cin>>n>>m;
memset(dp, 0,sizeof(dp));
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
func(x,y,z);
}
for(int i=1;i<=cnt;i++){
for(int j=n;j>=1 && j>=w[i];j--){
dp[j]=max(dp[j-w[i]]+v[i],dp[j]);
}
}
cout<<dp[n]<<endl;
}
return 0;
}
三、完全揹包
問題:
完全揹包
HihoCoder - 1043
有n種不同種類的硬幣,每種都有無限個。每種硬幣的價值不同,重量不同。現在你有一個袋子最多可裝m的重量,求在滿足重量要求的情況下得到的最大價值是多少。
解析
完全揹包問題。相比於01揹包比較“取”和“不取”,完全揹包需要比較"取多少",所以需要多一層循環。
設重量爲wi ,價值爲vi
基本方程爲:
定義:dp[i,j]表示前i種硬幣、袋子剩餘容量爲j時,可以得到的最大價值。
方程:
dp[i,j]=max( dp[i-1,j-k*w[i]]+v[i]*k ) , 0<=k<inf && w[i]*k<=j
1<=i<=n
1<=j<=m
空間壓縮後:
dp[j]=max( dp[j-k*w[i]]+v[i]*k ) , 0<=k<inf && w[i]*k<=j
1<=i<=n
m>=j>=w[i]
AC代碼:
//
// main.cpp
// test
//
// Created by dawn on 2019/8/26.
// Copyright © 2019 chuyi. All rights reserved.
//
#include <iostream>
#include <stdlib.h>
#include <cstring>
using namespace std;
int const maxn=1e5+10;
#define max(i,j) ((i)>(j)?(i):(j))
int v[maxn];
int w[maxn];
int dp[maxn];
/*
總容量 m 石頭種類數n
價值v
重量w
*/
int main(int argc, const char * argv[]) {
int n,m;
cin>>n>>m;
memset(dp, 0,sizeof(dp));
for(int i=1;i<=n;i++){
scanf("%d%d",w+i,v+i);
}
for(int i=1;i<=n;i++){
for(int j=m;j>=w[i];j--){
int max_k = j/w[i];
for(int k=0;k<=max_k;k++){
dp[j]=max(
dp[j],
dp[j-k*w[i]]+v[i]*k
);
}
}
}
cout<<dp[m]<<endl;
return 0;
}
優化?