背包dp整理


01背包

    動態規划是一種高效的算法。在數學和計算機科學中,是一種將復雜問題的分成多個簡單的小問題思想 ---- 分而治之。因此我們使用動態規划的時候,原問題必須是重疊的子問題。運用動態規划設計的算法比一般朴素算法高效很多,因為動態規划不會重復計算已經計算過的子問題。因為動態規划又可以稱為“記憶化搜索”。

    01背包是介紹動態規划最經典的例子,同時也是最簡單的一個。我們先看看01背包的是什么?

問題(01背包):
        有n個重量和價值分別為vi和ci的物品。從這些物品中挑出總重量不超過m的物品,求所有挑選方案中價值總和的最大值。

    這就是被稱為01背包的問題。在沒學習動態規划之前,我們看到這個問題第一反應會用dfs搜索一遍。那我們先使用這種方法來求解01背包問題:

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e3+1;
int n,m,v[N],c[N];
//從第i個物品開始挑選總重量不大於sumv的部分  
int dfs(int now,int sumv){
    int res;
    if(now==n+1) return res=0;//已經沒有剩余物品
    if(sumv<v[now]) res=dfs(now+1,sumv);//無法挑選第i個物品
    else res=max(dfs(now+1,sumv),dfs(now+1,sumv-v[now])+c[now]);//比較挑和不挑的情況,選取最大的情況
    return res;
}
int main(){
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&v[i],&c[i]);
    printf("%d",dfs(1,m));
    return 0;
}
/*
期望得分:30分 
*/

    乍一看dfs好像就可以解決這個問題,那還有動態規划什么事。然而我們仔細分析一下時間復雜度,每一種狀態都用選或者不選兩種可能。所以我們可以得出使用dfs的時間復雜度為O(2^n)。顯然這個方法不是一個很好的方法,因為這個時間復雜度太高了。我們仔細研究可以發現,造成時間復雜度這么高的原因是重復計算。既然我們找到復雜度這么高的原因,那我們就可以想辦法減少它重復計算的次數。仔細分析容易想到,使用一個二維數組來記錄每一次搜索的答案,這樣我們就避免了重復計算。

    這樣的小技巧,我們稱之為記憶化搜索。我們只是小小的改變就讓它的時間復雜度降低至O(nW)。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e3+1;
int n,m,v[N],c[N],dp[N][N*10];//保存每一次搜索的答案 
int dfs(int now,int sumv){
    if(dp[now][sumv]!=-1) return dp[now][sumv];
    int res;
    if(now==n+1) return dp[now][sumv]=0;
    if(sumv<v[now]) res=dfs(now+1,sumv);
    else res=max(dfs(now+1,sumv),dfs(now+1,sumv-v[now])+c[now]);
    return dp[now][sumv]=res;
}
int main(){
    memset(dp,-1,sizeof dp);//初始化dp數組的值,使其全為-1 
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&v[i],&c[i]);
    printf("%d",dfs(1,m));
    return 0;
}
/*
期望得分:60-80分 
*/

  仔細分析,可以發現我們還可以有更簡單的寫法(轉成遞推):

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e3+1;
int n,m,v[N],c[N],dp[N][N*10];
//dp[i][j] 表示取了i個物品挑選出總重量不超過j的物品時,背包中的最大價值
int main(){
    memset(dp,-1,sizeof dp);
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&v[i],&c[i]);
    for(int i=0;i<=m+1;i++) dp[n+1][i]=0;//還沒開始挑選的時候,背包里的總價值為0
    for(int i=n;i;i--){
        for(int j=m;j>=0;j--){
            if(j<v[i]) dp[i][j]=dp[i+1][j];
            else dp[i][j]=max(dp[i+1][j],dp[i+1][j-v[i]]+c[i]);
        }
    }
    printf("%d",dp[1][m]);
    return 0;
}
/*
期望得分:60-80分 
*/

  然后dp[i][j]的僅由dp[i+1][j]||dp[i+1][j-v[i]]轉移而來,於是我們可以滾動第一維:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e3+1;
int n,m,v[N],c[N],dp[2][N*10];
int main(){
    memset(dp,-1,sizeof dp);
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&v[i],&c[i]);
    int now=0;//開啟滾動 
    for(int i=0;i<=m;i++) dp[now][i]=0;//當前狀態初始化 
    for(int i=n;i;i--){
        now^=1;
        for(int j=m;j>=0;j--){
            if(j<v[i]) dp[now][j]=dp[now^1][j];
            else dp[now][j]=max(dp[now^1][j],dp[now^1][j-v[i]]+c[i]);
        }
    }
    printf("%d",dp[now][m]);
    return 0;
}
/*
期望得分:100分 
*/

  凡是可以滾動的,必定可以降維。——shenben

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e3+1;
int n,m,v[N],c[N],dp[N*10];
int main(){
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&v[i],&c[i]);
    dp[0]=0;
    for(int i=1;i<=n;i++){
        for(int j=m;j>=v[i];j--){//v[i]以后的j對答案沒有貢獻 
            dp[j]=max(dp[j],dp[j-v[i]]+c[i]);
        }
    }
    printf("%d",dp[m]);
    return 0;
}
/*
期望得分:100分 
*/

    使用遞推方程直接求解的方法,我們稱之為dp。因為他每一次的選取,都在動態的計算最優的情況。當然可能他局部不是最優,但是整體一定是最優解。這就是他和貪心算法最大的不同,貪心算法,每一次都是最優,但是整體不一定不是最優。

多重背包

 

問題(多重背包):
  就是一個0,1,2……k背包(往01背包上想就好了)
有n種重量和價值分別為vi和ci的物品,每種物品有si個。從這些物品中挑出總重量不超過m的物品,求所有挑選方案中價值總和的最大值

 

  搜索

//期望得分:30-70分 
#include<cstdio>
#include<algorithm>
#define N 10100
using namespace std;
int v[N],c[N],s[N],n,m;
int ans;
void dfs(int now,int sumv,int sumc){
    if(now==n+1){if(sumv<=m) ans=max(ans,sumc);return ;}
    dfs(now+1,sumv,sumc);
    for(int i=1;i<=s[now];i++) dfs(now+1,sumv+i*v[now],sumc+i*c[now]);
}
int main(){
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++) scanf("%d%d%d",v+i,c+i,s+i);
    dfs(1,0,0);
    printf("%d",ans);
    return 0;
}

  記憶化搜索

//期望得分:60-100分 
#include<cstdio>
#include<algorithm>
#define N 10100
using namespace std;
int v[N],c[N],s[N],n,m;
int ans;
int dp[101][N];
int dfs(int now,int sumv){
    if(now==n+1) return 0;
    if(dp[now+1][sumv]) dp[now][sumv]=dp[now+1][sumv];
    else dp[now][sumv]=dfs(now+1,sumv);
    for(int i=1;i<=s[now];i++){
        if(sumv+i*v[now]>m) break;
        if(dp[now+1][sumv+i*v[now]]) dp[now][sumv]=max(dp[now][sumv],dp[now+1][sumv+i*v[now]]+i*c[now]);
        else dp[now][sumv]=max(dp[now][sumv],dfs(now+1,sumv+i*v[now])+i*c[now]);
    }
    return dp[now][sumv];
}
int main(){
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++) scanf("%d%d%d",v+i,c+i,s+i);
    dfs(1,0);
    printf("%d",dp[1][0]);
    return 0;
}

  記憶化搜索轉遞推

//期望得分:60-100分 
#include<cstdio>
#include<algorithm>
#define N 10100
using namespace std;
int v[N],c[N],s[N],n,m;
int ans;
int dp[101][N];
int main(){
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++) scanf("%d%d%d",v+i,c+i,s+i);
    for(int i=n;i;i--){
        for(int j=m;j>=0;j--){
            for(int k=0;k<=s[i];k++){
                if(j-k*v[i]<0) break;
                dp[i][j]=max(dp[i][j],dp[i+1][j-k*v[i]]+k*c[i]);
            }
        }
    }
    printf("%d",dp[1][m]);
    return 0;
}

  滾動數組(滾動第一維)

//期望得分:100分 
#include<cstdio>
#include<algorithm>
#define N 10100
using namespace std;
int v[N],c[N],s[N],n,m;
int ans;
int dp[2][N];
int main(){
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++) scanf("%d%d%d",v+i,c+i,s+i);
    int now=0;
    for(int i=n;i;i--){
        now^=1;
        for(int j=m;j>=0;j--){
            for(int k=0;k<=s[i];k++){
                if(j-k*v[i]<0) break;
                dp[now][j]=max(dp[now][j],dp[now^1][j-k*v[i]]+k*c[i]);
            }
        }
    }
    printf("%d",dp[now][m]);
    return 0;
}

   降維(用二進制優化)

//期望得分:100分 
#include<cstdio>
#include<iostream>
using namespace std;
inline int read(){
    register int x=0,f=1;
    register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int N=1e5+10;
int n,m,cnt,xp[30];
int f[N*200],v[N],c[N];
int main(){
    for(int i=0;i<=25;i++) xp[i]=1<<i; 
    m=read();n=read();
    for(int i=1,x,y,z;i<=n;i++){
        x=read();y=read();z=read();
        for(int t=0;z>xp[t];t++){
            v[++cnt]=x*xp[t];
            c[cnt]=y*xp[t];
            z-=xp[t];
        }
        if(z>0){
            v[++cnt]=x*z;
            c[cnt]=y*z;
        }
    }
    for(int i=1;i<=cnt;i++){
        for(int j=m;j>=v[i];j--){
            f[j]=max(f[j],f[j-v[i]]+c[i]);
        }
    }
    printf("%d",f[m]);
    return 0;
}

其他背包dp自行整理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM