\(01背包詳解\) 順帶幾題完全背包問題
\(update:\)
2019.4.4 初稿。
2019.4.13 重改加上一些注釋 順便加幾道完全背包題目。以及調整Markdown。
本文涉及到的題目
\(\small \ P1048\ 采葯\)
\(\small \ P1049\ 裝箱問題\)
\(\small \ P1060\ 開心的金明\)
\(\small \ P1164\ 小A點菜\)
\(\small \ P2639\ [USACO09OCT]Bessie的體重問題\ Bessie's We…\)
\(\small \ P1794\ 裝備運輸_NOI導刊2010提高\ (04)\)
\(\small \ P1877\ [HAOI2012]音量調節\)
\(\small \ P1910\ L國的戰斗之間諜\)
\(\small \ P2871\ [USACO07DEC]手鏈Charm\ Bracelet\)
\(\small \ P1455\ 搭配購買\)
\(\small \ P1616\ 瘋狂的采葯\)
\(\small \ P2722\ 總分 Score Inflation\)
\(\small \ P2918\ [USACO08NOV]買干草Buying\ Hay\)
前言:DP 快接觸半年了。 還是想起來把曾經
虐我的\(01背包\) 好好寫寫。
下面借鑒此處
其中\(F[i-1][j]\)表示前\(i-1\)件物品中選取若干件物品放入剩余空間為\(j\)的背包中所能得到的最大價值
而\(F[i-1][j-C_i]+W_i\)表示前\(i-1\)件物品中選取若干件物品放入剩余空間為\(j-C_i\)的背包中所能取得的最大價值加上第\(i\)件物品的價值
根據第 \(i\) 件物品放或是不放確定遍歷到第 \(i\) 件物品時的狀態\(F[i][j]\)
設物品件數為\(N\) 背包容量為\(V\) 第 \(i\)件物品體積為\(C_i\) 第\(i\)件物品價值為\(W_i\)
所以得出代碼
for(register int i=1; i<=n; i++)
for(register int j=m; j>=c[i]; j--) dp[i][j] = max(dp[i-1][j-c[i]] + w[i] , dp[i][j]) ;
\[F[i][j]只與F[i-1][j]和F[i-1][j-C_i]有關 \]即只和\(i-1\)時刻狀態有關 所以我們只需要用一維數組\(F[]\)來保存\(i-1\)時的狀態\(F[]\)。
假設\(i-1\)時刻的\(F[]\)為\({a[0]\ a[1]\ a[2]… \ a[v]}\)
那么\(i\)時刻的\(F[]\)中第\(k\)個應該為\(max(a_k,a[k]-C[i]+W_i)\)
即 \(max(F_k,F[k-C_i]+W_i)\)
這就需要我們遍歷\(V\)時逆序遍歷
這樣才能保證求 \(i\)時刻 \(F[k]\) 時 \(F[k-C_i]\) 是 \(i-1\) 時刻的值
如果正序遍歷則當求\(F[k]\)時其前面的\(F_0\ F_1 \ F_k-1\)都已經改變過,里面存的都不是\(i-1\)時刻的值,這樣求\(F_k\)時利用\(F[K-C_i]\)必定是錯的值。最后\(F_v\)即為最大價值。
下面的完全背包會仔細解釋為什么是倒序遍歷(\(update\ on\ 4.13\))
for(register int i=1; i<=n; i++)
for(register int j=m; j>=c[i]; j--) dp[j] = max(dp[j-c[i]] + w[i] , dp[j]) ;
雖然時間復雜度不變 但是內存減少很多了。
=========================================update:大致模板
#include <bits/stdc++.h>
#define rep(i,j,n) for(register int i=j;i<=n;i++)
#define Rep(i,j,n) for(register int i=j;i>=n;i--)
#define low(x) x&(-x)
using namespace std ;
typedef long long LL ;
const int inf = INT_MAX >> 1 ;
inline LL In() { LL res(0) , f(1) ; register char c ;
#define gc c = getchar()
while(isspace(gc)) ; c == '-' ? f = - 1 , gc : 0 ;
while(res = (res << 1) + (res << 3) + (c & 15) , isdigit(gc)) ;
return res * f ;
#undef gc
}
int n , m ;
const int N = 100000 + 5 ;
int f[N] ; //數組
inline void Ot() {
n = In() , m = In() ;
for(register int i=1;i<=n;i++) {
int w = In() , c = In() ; //輸入 體積&&價值
for(register int j=m;j>=w;j--)
f[j] = max(f[j] , f[j-w] + c) ; //狀態轉移方程
}
cout << f[m] << endl ; //f[m] 是 目標值。 所以輸出。
}
signed main() {
// freopen("test.in","r",stdin) ;
return Ot() , 0 ;
}
可以根據這個代碼來模擬過程。
=========================================例題。
P1048 采葯
P1049 裝箱問題
P1060 開心的金明
P1164 小A點菜
P2639 [USACO09OCT]Bessie的體重問題Bessie's We…
=========================================
這題就比較裸了。直接套進去板子。
#include <bits/stdc++.h>
#define rep(i,j,n) for(register int i=j;i<=n;i++)
#define Rep(i,j,n) for(register int i=j;i>=n;i--)
#define low(x) x&(-x)
using namespace std ;
typedef long long LL ;
const int inf = INT_MAX >> 1 ;
inline LL In() { LL res(0) , f(1) ; register char c ;
#define gc c = getchar()
while(isspace(gc)) ; c == '-' ? f = - 1 , gc : 0 ;
while(res = (res << 1) + (res << 3) + (c & 15) , isdigit(gc)) ;
return res * f ;
#undef gc
}
int T , M ;
const int N = 1000 + 5 ;
int dp[N] ;
inline void Ot() {
T = In() , M = In() ;
for(register int i=1;i<=M;i++) {
int x = In() , y = In() ;
for(register int j=T;j>=x;j--) dp[j] = max(dp[j-x]+y, dp[j]) ;
}
cout << dp[T] << endl ;
}
signed main() {
return Ot() , 0 ;
}
這題轉移方程略有不同。 重量就是價格(應該能這么理解)
所以 \(W[i] = C[i]\) 所以轉移方程為\(dp[j]\ = \ max \ ( dp[j]\ ,\ dp[j-w[i]]\ +\ w[i]);\)
#include<bits/stdc++.h>
#define mx 25000
using namespace std;
int w[mx],n,V,dp[mx];
int main() {
ios::sync_with_stdio(false);
cin>>V>>n;
for(int i=1; i<=n; i++) {
cin>>w[i];
}
for(int i=1; i<=n; i++) {
for(int j=V; j>=w[i]; j--) {
dp[j]=max(dp[j],dp[j-w[i]]+w[i]);
}
}
int ans=0;
for(int i=1; i<=V; i++) {
ans=max(ans,dp[i]);
}
cout<<V-ans<<'\n';
return 0;
}
這題就是把模板套一下。 價值先預處理出來。
#include<bits/stdc++.h>
#define f(i,j,n) for(int i=j;i<=n;i++)
#define fa(i,j,n) for(int i=j;i>=n;i--)
using namespace std;
int w[30],v[30],f[50000];
int n,m;
int main() {
cin>>m>>n;
f(i,1,n) {
cin>>v[i]>>w[i];
w[i]*=v[i];
}
f(i,1,n)
fa(j,m,v[i]) f[j]=max(f[j],f[j-v[i]]+w[i]);
cout<<f[m]<<endl;
return 0;
}
01背包的變式 \(f_j\ =\ f_j\ +\ f[j-w_i];\)
#include <bits/stdc++.h>
#define rep(i,j,n) for(register int i=j;i<=n;i++)
#define Rep(i,j,n) for(register int i=j;i>=n;i--)
#define low(x) x&(-x)
using namespace std ;
typedef long long LL ;
const int inf = INT_MAX >> 1 ;
inline LL In() { LL res(0) , f(1) ; register char c ;
#define gc c = getchar()
while(isspace(gc)) ; c == '-' ? f = - 1 , gc : 0 ;
while(res = (res << 1) + (res << 3) + (c & 15) , isdigit(gc)) ;
return res * f ;
#undef gc
}
int n , m ;
const int N = 100 + 5 ;
const int M = 10000 + 5 ;
int w[N] ;
int f[M] ;
inline void Ot() {
n = In() , m = In() ;
for(register int i=1;i<=n;i++) w[i] = In() ;
f[0] = 1 ;
for(register int i=1;i<=n;i++)
for(register int j=m;j>=w[i];j--) f[j] += f[j-w[i]] ;
cout << f[m] << endl ;
}
signed main() {
// freopen("test.in","r",stdin) ;
return Ot() , 0 ;
}
重量就是價格
所以 \(W_i = C_i\) 所以轉移方程為\(dp_j\ = \ max \ ( dp_j\ ,\ dp[j-w_i]\ +\ w_i);\)
但是這里賦值過了 所以就沒什么關系了 直接跑\(01\)背包。
#include<iostream>
using namespace std;
int f[450001],w[100001],c[100001],n,m;
int main() {
ios::sync_with_stdio(false);
cin>>m>>n;
for(int i=1; i<=n; i++) {
cin>>c[i];
w[i]=c[i];
}
for(int i=1; i<=n; i++) {
for(int j=m; j>=c[i]; j--) {
if(f[j-c[i]]+w[i]>f[j])
f[j]=f[j-c[i]]+w[i]; //這里其實應該是max 當時就這么寫了。
}
}
cout<<f[m];
return 0;
}
=========================================二維例題。
P1794 裝備運輸_NOI導刊2010提高(04)
P1877 [HAOI2012]音量調節
P1910 L國的戰斗之間諜
P2871 [USACO07DEC]手鏈Charm Bracelet
=========================================
同上面說明
#include <bits/stdc++.h>
#define rep(i,j,n) for(register int i=j;i<=n;i++)
#define Rep(i,j,n) for(register int i=j;i>=n;i--)
#define low(x) x&(-x)
using namespace std ;
typedef long long LL ;
const int inf = INT_MAX >> 1 ;
inline LL In() { LL res(0) , f(1) ; register char c ;
#define gc c = getchar()
while(isspace(gc)) ; c == '-' ? f = - 1 , gc : 0 ;
while(res = (res << 1) + (res << 3) + (c & 15) , isdigit(gc)) ;
return res * f ;
#undef gc
}
int v , g ;
int n ;
const int N = 500 ;
int dp[N][N] ;
inline void Ot() {
v = In() ;
g = In() ;
n = In() ;
for(register int i=1;i<=n;i++) {
int c = In() ;
int x = In() , y = In() ;
for(register int j=v;j>=x;j--)
for(register int k=g;k>=y;k--) dp[j][k] = max(dp[j][k] , dp[j-x][k-y] + c) ;
}
cout << dp[v][g] << endl ;
}
signed main() {
// freopen("testdata.txt","w",stdout) ;
return Ot() , 0 ;
}
這題在某谷上寫過\(題解\) 了。
#include <bits/stdc++.h>
#define rep(i,j,n) for(register int i=j;i<=n;i++)
using namespace std;
typedef long long LL;
inline LL read(){ LL x=0;int f(1);char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return x*f;
}
int n;
int Begin,Max;
int a[1<<6];
int dp[1<<6][1<<12];
signed main(){
memset(dp,0,sizeof(dp));
n=read(); Begin=read(); Max=read();
dp[0][Begin]=1;
rep(i,1,n) a[i]=read();
rep(i,1,n) rep(j,0,Max) {
if (j+a[i] <= Max) dp[i][j]=dp[i][j]||dp[i-1][j+a[i]];
if (j-a[i] >= 0) dp[i][j]=dp[i][j]||dp[i-1][j-a[i]];
}
LL ans = -0x7f;
for(register int i=1;i<=Max;i++) {
if(dp[n][i]) ans = i;
}
if ( ans != -0x7f) cout << ans << endl ;
else puts("-1") ;
return 0;
}
根據上面這部分。 把幾個變量整合到一塊。
#include <bits/stdc++.h>
#define rep(i,j,n) for(register int i=j;i<=n;i++)
#define Rep(i,j,n) for(register int i=j;i>=n;i--)
#define low(x) x&(-x)
using namespace std ;
typedef long long LL ;
const int inf = INT_MAX >> 1 ;
inline LL In() { LL res(0) , f(1) ; register char c ;
#define gc c = getchar()
while(isspace(gc)) ; c == '-' ? f = - 1 , gc : 0 ;
while(res = (res << 1) + (res << 3) + (c & 15) , isdigit(gc)) ;
return res * f ;
#undef gc
}
int v , m ;
int n ;
const int N = 1000 + 5 ;
int dp[N][N] ;
inline void Ot() {
n = In() ;
v = In() , m = In() ;
rep(i,1,n) {
int x = In() , y = In() , z = In() ;
Rep(i,v,y)
Rep(j,m,z) dp[i][j] = max(dp[i-y][j-z] + x , dp[i][j]) ;
}
cout << dp[v][m] << endl ;
}
signed main() {
return Ot() , 0 ;
}
#include <bits/stdc++.h>
#define rep(i,j,n) for(register int i=j;i<=n;i++)
#define Rep(i,j,n) for(register int i=j;i>=n;i--)
#define low(x) x&(-x)
using namespace std ;
typedef long long LL ;
const int inf = INT_MAX >> 1 ;
inline LL In() { LL res(0) , f(1) ; register char c ;
#define gc c = getchar()
while(isspace(gc)) ; c == '-' ? f = - 1 , gc : 0 ;
while(res = (res << 1) + (res << 3) + (c & 15) , isdigit(gc)) ;
return res * f ;
#undef gc
}
int v , m ;
int n ;
const int N = 400 + 5 ;
int dp[N][N] ;
inline void Ot() {
v = In() , m = In() ;
n = In() ;
rep(i,1,n) {
int x = In() , y = In() , z = In() ;
Rep(i,v,x)
Rep(j,m,y) dp[i][j] = max(dp[i-x][j-y] + z , dp[i][j]) ;
}
cout << dp[v][m] << endl ;
}
signed main() {
return Ot() , 0 ;
}
帶點[並查集]的
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
inline LL read () { LL res = 0 ;int f (1) ;char ch = getchar ();
while (!isdigit(ch)) { if (ch == '-') f = -1 ;ch = getchar();}
while (isdigit(ch)) res = (res << 1) + (res << 3) + (ch ^ 48) ,ch = getchar(); return res * f ;
}
int n,m,we,fa[10005],w[10005],c[10005],dp[10005];
inline int find(int x){
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
inline void merge(int x,int y){
int f1=find(x),f2=find(y);
if(f1!=f2)fa[f1]=f2,c[f2]+=c[f1],w[f2]+=w[f1];
}
int main(){
n=read(),m=read(),we=read();
for(register int i=1;i<=n;i++) fa[i]=i;
for(register int i=1;i<=n;i++) c[i]=read(),w[i]=read();
for(register int i=1;i<=m;i++){
int u=read(),v=read();
merge(u,v);
}
for(register int i=1;i<=n;i++) if(fa[i]==i)
for(register int j=we;j>=c[i];j--) dp[j]=max(dp[j],dp[j-c[i]]+w[i]);
cout << dp[we] << endl ;
return 0;
}
HDU - 2026 - Bone Collector
HDU - 2546 - 飯卡
HDU - 2955 - Robberies
HDU - 1203 - I NEED A OFFER!
HDU - 1171 - Big Event in HDU
====================================================完全背包。(\(update\ on\ 4.13\))
那么我們再次仔細思考。 為什么01背包要反過來?
就假設是正過來的好了吖。
正序遍歷 是 \(for(register\ int\ i \ =\ w[i];i<=m;i++)\)
根據循環條件 我們可以得知 \(w_i\) 可以放 \(inf\) 次(假設\(m=∞\))
這樣子的話。就是完全背包。
\(∵01背包一件物品只能放一次\)
\(∴就只能更新一次值\ 所以是\ from\ w_i \ to \ m\)
=========================================例題。
P1616 瘋狂的采葯
P2722 總分 Score Inflation
\(P2918\ [USACO08NOV]買干草Buying\ Hay\)
=========================================
內循環倒過來。 因為這是一個完全背包(也是01背包的變式
在 <采葯> 中,每種草葯只允許 采一次 。(所以是標准的01背包 上面也有了。
我們將采 第\(i\)種草葯 所需的 時間 設為 \(t_i\) 價值 設為 \(p_i\)
如果有一個數組 \(f[i][j]\) 來表示 從前往后 到第\(i\)種草葯 (當然前面可能有草葯不采) ,花費了最多 \(j\) 時間 (意思是可能花費了少於\(j\)時間) 時 能采到草葯的 最大價值
到第\(i\)種草葯時有兩種情況:采與不采。
若不采這種草葯,則 時間花費沒有增多 ,經過的 草葯種數增加了\(1\) , 采到草葯價格不變 ,所以 \(f[i][j]=f[i-1][j]\) ;
若采這種草葯 則 時間花費增加了\(t_i\) 種數增加\(1\) 采到草葯價格增加了\(p_i\),所以 \(f[i][j]=f[i-1][j-t_i]+p_i\) 。
我們當然要使 \(f[i][j]\)盡可能大 即有 \(f[i][j]=max(f[i-1][j],f[i-1][j-t_i]+p_i)\)
#include <bits/stdc++.h>
#define rep(i,j,n) for(register int i=j;i<=n;i++)
#define Rep(i,j,n) for(register int i=j;i>=n;i--)
#define low(x) x&(-x)
using namespace std ;
typedef long long LL ;
const int inf = INT_MAX >> 1 ;
inline LL In() { LL res(0) , f(1) ; register char c ;
#define gc c = getchar()
while(isspace(gc)) ; c == '-' ? f = - 1 , gc : 0 ;
while(res = (res << 1) + (res << 3) + (c & 15) , isdigit(gc)) ;
return res * f ;
#undef gc
}
int t , m ;
const int T = 100000 + 5 ;
LL f[T] ;
inline void Ot() {
t = In() , m = In() ;
rep(i,1,m) {
int x = In() , y = In() ;
rep(j,x,t) f[j] = max(f[j] , f[j-x] + y) ;
}
cout << f[t] << endl ;
}
signed main() {
// freopen("test.in","r",stdin) ;
return Ot() , 0 ;
}
這和P1616 瘋狂的采葯 基本是一樣的
都是完全背包。
#include<bits/stdc++.h>
#define f(i,j,n) for(int i=j;i<=n;i++)
using namespace std;
int V, n;
int a[10001],b[10001],f[10001];
void read(int &x) {
int f=1;
x=0;
char s=getchar();
while(s<'0' or s>'9') {
if(s=='-') f=-1;
s=getchar();
}
while(s>='0' and s<='9') {
x=x*10+s-'0';
s=getchar();
}
x*=f;
}
int main() {
read(V),read(n);
f(i,1,n) read(a[i]),read(b[i]);
f(j,1,n)
f(k,b[j],V) f[k]=max(f[k],f[k-b[j]]+a[j]);
cout<<f[V]<<endl;
return 0;
}
這題就有點坑了。
坑點1 :要用最小值。所以初值賦值最大值。 但是 \(dp_0 = 0\) 不然如何都是 最大值
坑點2 :目標不是\(dp_h\) 而是 \(max\) \(dp_h...dp_{h+5000}\) 因為
第\(i\)公司賣的干草包重量 為\(P_i (1<=P_i<=5000)\)磅,需要的開銷為\(C_i (1<C_i <=5000)\)美元
沒准 多一丟丟還便宜點。(大霧。
\(code\)
#include <bits/stdc++.h>
#define rep(i,j,n) for(register int i=j;i<=n;i++)
#define Rep(i,j,n) for(register int i=j;i>=n;i--)
#define low(x) x&(-x)
using namespace std ;
typedef long long LL ;
const int inf = INT_MAX >> 1 ;
inline LL In() { LL res(0) , f(1) ; register char c ;
#define gc c = getchar()
while(isspace(gc)) ; c == '-' ? f = - 1 , gc : 0 ;
while(res = (res << 1) + (res << 3) + (c & 15) , isdigit(gc)) ;
return res * f ;
#undef gc
}
int n , h ;
const int N = 55000 + 5 ;
int dp[N] ;
inline void Ot() {
n = In() , h = In() ;
memset(dp,0x7f,sizeof(dp)) ;
dp[0] = 0 ;
rep(i,1,n) {
int x = In() , y = In() ;
rep(j,x,h+5000) dp[j] = min(dp[j] , dp[j-x] + y) ;
}
int ans = 0x7f7f7f7f7f ;
rep(i,h,h+5000) ans = min(ans , dp[i]) ;
cout << ans << endl ;
}
signed main() {
// freopen("test.in","r",stdin) ;
return Ot() , 0 ;
}