动态规划与背包问题 —— [Acwing]Problem2 01背包问题

aaf2aea59f0146c59649e03c1557f0f3.png

昨天我的舍友给我推荐了一道题:

题目 01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 eq?v_i,价值是 eq?w_i

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入

第一行两个整数 N,V用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 eq?v_i%2Cw_i ,用空格隔开,分别表示第 i 件物品的体积和价值。

输出

输出一个整数,表示最大价值。

数据范围

0<N,V≤10000
0<eq?v_i%2Cw_i≤1000

当时正好学了深度优先搜索,我就说,这道题应该是搜索。舍友说看到题解用到了动态规划和状态转移之类的,不认为深度优先搜索能解决这个问题。我便回到座位上尝试用搜索解决这类问题,很快啊!我就写出了如下代码:

#include<bits/stdc++.h>
#include<algorithm>
using namespace std;
int currentBest = 0;
int ans=0;

struct item{
    int v,w;
    int index;
    item(int iptV,int iptW,int iptIndex){
        v=iptV;w=iptW;index=iptIndex;
    }
};

struct bag{
    vector<item> items;
    int V=0;
    int W=0;
    bag(){}
};

void addItem(item iptItem,bag& iptBag){
    iptBag.V = iptBag.V + iptItem.v;
    iptBag.W = iptBag.W + iptItem.w;
    iptBag.items.push_back(iptItem);
}

void deleteItem(int index,bag& iptBag){
    item iptItem = iptBag.items[index];
    iptBag.V = iptBag.V - iptItem.v;
    iptBag.W = iptBag.W - iptItem.w;
}

void bfs(bag itemSet,int start,int maxV){
    int N = itemSet.items.size();
    bool found = true;
    for(int i=start;i<N;i++){
        bag nextSet = itemSet;
        deleteItem(i,nextSet);
        if(nextSet.V <= maxV && nextSet.W >= currentBest){
            currentBest = nextSet.W;
            found = false;
            ans++;
        }
        else if(nextSet.V > maxV){ 
            found = false;
        }
        if(!found) bfs(nextSet,i+1,maxV);
    }
    return;
}

int main(){
    int N,maxV;
    cin >> N >> maxV;
    bag Allthings = bag();
    for(int i=0;i<N;i++){
        int v,w;
        cin >> v >> w;
        addItem(item(v,w,i),Allthings);
    }
    if(Allthings.V <= maxV){
        cout << Allthings.W << endl;
        return 0;
    }
    bfs(Allthings,0,maxV);
    cout << currentBest << endl;
    // cout << ans;
    return 0;
}

思路是逐个排除物品,看是否能使总体积小于等于背包体积,如果能,去比较总价值与目前最优的大小关系。

我还做了一定的优化:如果去除k+1个物品得到的所有结果都没有去除k个物品的结果好,就不必再向下搜索,避免了无用的计算。在测试用例一中,只搜索了两次就能得到最终结果。

ab66364f8c644c37a9a054591eb53a65.png

去掉 ans 的注释 第二行就是搜索的次数

但这远远承受不了更庞大的数据量,Submit后只能获得超时的结果,是时候去学习一个新的知识点了:

定义 动态规划

通过把原问题分解成若干相关子问题(所有这些问题在一定意义上答案固定),再利用已知子问题的答案依次计算未知问题答案,最终得到原问题答案的方式,称之为动态规划。

定义 状态

动态规划的状态可以笼统的解释成“问题所在的局面”。一般可以写成: (用变量表示的)所在局面的(最优)答案,或者满足某种性质的方案数。

状态的答案应该只依赖于状态定义的局面和一些状态以外的常量,也就是说,在一般情况下,状态变化并不会影响状态以外的量,该状态之后的演变不受这个状态之前决策的影响,因此如果要用动态规划来解决一道题,请把所有可能的变动加入到状态的定义中去。因为要求解最终状态的答案,所以状态之间需要存在某些关系。一般把这样的计算关系称作转移。

状态转移方程反映了状态与状态之间的关系,类似于递归。利用动态规划解决问题的核心是根据题目给状态一个良好的定义,个人理解:状态定义的局面(即自变量x)能快速得到状态的答案(f(x))。本题中,我们需要处理的问题是在有限的空间V里尽可能装多价值的物品,物品各不相同且都只有一个。什么可以作为自变量x呢?可以尝试去思考一下,例如可以考虑把选择装的物品的集合X作为自变量,自然是可以写出如下的状态转移方程:

eq?f%28X%2CV%29%3D%5Cmax%5Cbegin%7BBmatrix%7D%20f%28X-x_1%2CV-v_1%20%29&plus;w_1%2Cf%20%28X-x_2%2CV-v_2%20%29&plus;w_2%2C...%2Cf%28X-x_n%2CV-v_n%20%29&plus;w_n%20%5Cend%7BBmatrix%7D

f(X,V)为在集合X里选取物品的所有合法方式(总物品体积小于等于V)中的最大价值。

写出状态转移方程,就算是能看懂题目了,初学者可以选取多个状态定义的局面去写状态转移方程,拓宽问题角度,提高对状态这一词的理解。

这样的状态转移方程自然是可以用于编程中的,但枚举的情况相当多。这个状态转移方程定义的没问题,但是状态的局面选取的不够好。对于0-1背包问题,一种常规的思路是:

定义状态的局面为可能用的物品的范围(可能选取前i个物品),状态的答案是只在前i个物品中选取物品装入容积为V的背包所能产生的最大价值,可以得到状态转移方程:

eq?f%28i&plus;1%2CV%29%3D%5Cmax%20%5Cbegin%7BBmatrix%7D%20f%28i%2CV-v_i%29&plus;w_%7Bi&plus;1%7D%2Cf%28i%2CV%29%20%5Cend%7BBmatrix%7D

我们只需要使用二维数组就能很快得出答案,代码实现如下:

#include<bits/stdc++.h>
using namespace std;

int main(){
    int N,V;
    cin >> N >> V;
    vector<vector<int>> itemSet;
    for(int i=0;i<N;i++){
        int v,w;
        cin >> v >> w;
        itemSet.push_back({v,w});
    }
    vector<vector<int>> f(N,vector<int>(V+1,0));
    for(int j=0;j<=V;j++){
        if(itemSet[0][0]<=j)f[0][j]=itemSet[0][1];
    }
    for(int i=1;i<N;i++){
        for(int j=0;j<itemSet[i][0];j++){
            f[i][j] = f[i-1][j];
        } for(int j=itemSet[i][0];j<=V;j++){
            f[i][j] = max(f[i-1][j],f[i-1][j-itemSet[i][0]] + itemSet[i][1]);
        }
    }
    cout << f[N-1][V];
    return 0;
}

注意:需要对只选第一个物品的情况进行初始化(即代码中的f[0][j]的情形)

本解答的空间复杂度为eq?O%28NV%29,实际上可以优化到eq?O%28V%29,留作习题。

感谢你能看到这里。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值