动态规划——0-1背包

1 基础知识点

        首先设置一个二维数组dp[][],令dp[i][j]表示前i个物品装进容量为j的背包能获得的最大价值。通过设置这么一个二维数组,dp[n][m]的值就是0-1背包问题的解。

        只考虑第i件物品时,可将情况分为  是否放入第i件物品   两种:

  1. 对于容量为j的背包,如果不放入第i件物品,那么这个问题就转换成将前i-1个物品放入容量为j的背包的问题,即dp[i][j] = dp[i-1][j]。
  2. 对于容量为j的背包,如果放入第i件物品,那么当前背包的容量就变成了j-w[i],并得到这个物品的价值v[i]。之后这个问题就转化成将前i-1个物品放入容量为j-w[i]的背包问题,即dp[i][j] = dp[i-1][j-w[i]]+v[i]。

        从以上两种情况可以得到状态转移方程:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i])。转移时要注意j-w[i]的值是否为非负值,若为负则代表当前的容量无法放入第i件物品,不能进行转移。

        边界情况处理:dp[i][0] = dp[0][j] = 0  (0<=i<=n,  0<=j<=m)

        观察状态转移的特点,可以发现dp[i][j]的转移仅与dp[i-1][j-w[i]]和dp[i-1][j]有关,即仅与二维数组中本行的上一行有关。根据这个特点,可以将原本的二维数组优化为一维数组,并用如下的方式完成状态转移:dp[j] = max(dp[j], dp[j-w[i]]+v[i])。为了保证状态正确转移,必须保证在每次更新中确定状态dp[j]时,dp[j-w[i]]尚未被本次更新修改。这就需要在每次更新中,倒序遍历所有j值,因为只有这样才能保证在确定dp[j]的值时,dp[j-w[i]]的值尚未被修改,从而完成正确的状态转移。

例题——点菜问题

代码

#include <iostream>
using namespace std;
int main(){
    int C,N;
    while(cin>>C>>N){
        int dp[1001];
        int w[101],v[101];
        for(int i=1;i<=N;i++)
            cin>>w[i]>>v[i];
        for(int i=0;i<=C;i++)
            dp[i]=0;
        for(int i=1;i<=N;i++){
            for(int j=C;j>=w[i];j--){
                dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
            }
        }
        cout<<dp[C]<<endl;
    }
    return 0;
}

例题——最小邮票数

解法1(自己想的)

        dp[i][j]表示前i张邮票,恰能凑到j值所需要的最小张数,刚开始全部初始为-1

代码

#include <iostream>
using namespace std;
int main(){
    int M,N;
    while(cin>>M>>N){
        int dp[100],val[20];
        for(int i=1;i<=N;i++)
            cin>>val[i];
        for(int i=0;i<=M;i++)
            dp[i]=-1;
        for(int i=1;i<=N;i++){
            for(int j=M;j>=val[i];j--){
                int res = -1;
                if(dp[j]!=-1) res = dp[j];
                if(dp[j-val[i]]!=-1){
                    if(res==-1) res = dp[j-val[i]]+1;
                    else res = min(res,dp[j-val[i]]+1);
                }
                if(j-val[i]==0) res = 1;
                dp[j] = res;
            }
        }
        if(dp[M]==-1) cout<<0<<endl;
        else cout<<dp[M]<<endl;
    }
    return 0;
}

解法2(网上大部分人的解题)

  1. 邮票总值相当于背包容量
  2. 邮票的面值相当于物品的重量
  3. 邮票的数量相当于物品的总价值
  4. 每张邮票的价值默认为1

代码

#include <iostream>
#include <climits>
using namespace std;
int main(){
    int M,N;
    while(cin>>M>>N){
        int dp[100],val[20];
        for(int i=1;i<=N;i++)
            cin>>val[i];
        for(int i=1;i<=M;i++)
            dp[i]=INT_MAX-1;   //定义初值,因为求最小值,将默认值设较大一些
        dp[0]=0;  // 这一步很关键
        for(int i=1;i<=N;i++){
            for(int j=M;j>=val[i];j--){
                dp[j] = min(dp[j],dp[j-val[i]]+1);
            }
        }
        if(dp[M]==INT_MAX-1) cout<<0<<endl;
        else cout<<dp[M]<<endl;
    }
    return 0;
}

4 例题——PAT A1068(暂时还未通过测试用例)

暂时还未通过测试用例

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN=1e4+1;
const int MAXM=1e2+1;
// 若前i个物品能恰好装满容量为j的背包,则dp[i][j]=1;反之,dp[i][j]=0
int arr[MAXN],dp[MAXM];
vector<int> pre[MAXM];  // pre[j]:能到达j容量的所有前驱容量
vector<int> path,temp;
bool Jud() { // 判断temp路径是否可以更新为最优路径
    if(path.size()==0)
        return true;
    int len1=path.size(),len2=temp.size();
    int i=len1-1,j=len2-1;
    while(i>=0 && j>=0) {
        if(path[i]!=temp[j])
            return temp[j] < path[i];
        i--,j--;
    }
    return false;
}
void DFS(int m) { // 寻找最优路径
    if(m==0) {
        if(Jud()) {
            path.clear();
            int len = temp.size();
            for(int i=0; i<len; i++)
                path.push_back(temp[i]);
        }
    } else {
        for(int i=0; i<pre[m].size(); i++) {
            temp.push_back(m-pre[m][i]);
            DFS(pre[m][i]);
            temp.pop_back();
        }
    }
}
int main() {
    int N,M;
    cin>>N>>M;
    for(int i=1; i<=N; i++)
        cin>>arr[i];
    sort(arr+1,arr+N+1);
    fill(dp,dp+N+1,0);
    for(int i=1; i<=N; i++) {
        for(int j=M; j>=arr[i]; j--) {
            if(j==arr[i] || dp[j-arr[i]]==1) {
                dp[j]=1;
                pre[j].push_back(j-arr[i]);
            }
        }
    }
    if(pre[M].size()==0)
        cout<<"No Solution"<<endl;
    else {
        DFS(M);
        int len = path.size();
        for(int i=len-1; i>=0; i--) {
            if(i!=len-1)
                cout<<" ";
            cout<<path[i];
        }
        cout<<endl;
    }
    return 0;
}

5 总结

对能够划分阶段的问题来说,都可以尝试把阶段作为状态的一维,这样可以使我们更方便地得到满足无后效性的状态。

从滚动数组中可以得到启发:

如果当前设计的状态不满足无后效性,那么不妨把状态进行升维,即增加一维或若干维来表示相应的信息,这样可能就满足无后效性了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值