zoukankan      html  css  js  c++  java
  • 01背包详解

    (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背包) 好好写写。

    [large dp[i][j] = max( dp[i-1][j-w_i] + c_i , dp[i][j]); ]

    [这个方程熟悉吗qwq ]

    [以及下面这个压维的。 ]

    [large dp_j = max( dp[j-w_i] + c_i , dp_j); ]

    下面借鉴此处

    其中(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…
    =========================================

    P1048 采药

    这题就比较裸了。直接套进去板子。

    #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 ;
    }
    
    

    P1049 装箱问题

    这题转移方程略有不同。 重量就是价格(应该能这么理解)
    所以 (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<<'
    ';
    	return 0;
    }
    

    P1060 开心的金明

    这题就是把模板套一下。 价值先预处理出来。

    #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;
    }
    

    P1164 小A点菜

    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 ;
    }
    

    P2639 [USACO09OCT]Bessie的体重问题Bessie's We…

    重量就是价格
    所以 (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;
    }
    

    [引入:01背包一般没有几个是裸题。 ]

    [一般会带有两个状态 如果是真的裸题一般就是普及-的难度(除非该题意思很难理解233 ]

    [两个状态就必须要用二维 三维可能会爆内存 惨遭MLE 就GG了。em ]

    [两个状态 比如说一个是质量 一个是体积。n件物品。 ]

    [那么必须要把(F[N][M][V]) - > (F[M][V]) (其中M表示最大能承受的质量 V表示最大能承受的体积) ]

    =========================================二维例题。
    P1794 装备运输_NOI导刊2010提高(04)
    P1877 [HAOI2012]音量调节
    P1910 L国的战斗之间谍
    P2871 [USACO07DEC]手链Charm Bracelet
    =========================================

    P1794 装备运输_NOI导刊2010提高(04)

    同上面说明

    #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 ;
    }
    

    P1877 [HAOI2012]音量调节

    这题在某谷上写过(题解) 了。

    #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;
    }
    

    P1910 L国的战斗之间谍

    根据上面这部分。 把几个变量整合到一块。

    #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 ;
    }
    
    

    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 , 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 ;
    }
    
    

    带点[并查集]的

    P1455 搭配购买

    #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的01背包多得是 ]

    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)
    =========================================

    P1616 疯狂的采药

    内循环倒过来。 因为这是一个完全背包(也是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 ;
    }
    
    

    P2722 总分 Score Inflation

    这和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;
    }
    

    (P2918 [USACO08NOV]买干草Buying Hay)

    这题就有点坑了。
    坑点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 ;
    }
    
  • 相关阅读:
    源码分析
    HR不会告诉你的秘密
    文档生产工具 Doxygen
    Win7设置共享文件夹
    ZigBee NV层使用
    ZigBee2006,2007,pro各个版本的区别
    第一百九十三节,jQuery EasyUI,Draggable(拖动)组件
    第一百九十二节,jQuery EasyUI 使用
    第一百九十一节,jQuery EasyUI 入门
    第一百九十节,jQuery,编辑器插件
  • 原文地址:https://www.cnblogs.com/qf-breeze/p/01_beibao.html
Copyright © 2011-2022 走看看