题解

题目难度:中等

知识点:字符、动态规划、数组、递归、记忆化搜索

方法(一)动态规划

解析问题:本题可以分析为典型的01背包问题,使用动态规划就可以解决问题。在分析问题时,主要解析为以下三步。
第一步:确定【状态】和【选择】。
在本题中,【状态】就是“剩余的空间大小总和”和“可选择的资产”,【选择】就是“放入这个资产”和“不放入这个资产”。
第二步:要明确 dp 数组的定义。
第三步:根据【选择】,对状态转换的逻辑进行计算。

思路

第二步定义数组意义:在这里,使用二维的思维创建dp的形式。设置 dp[i][j] ,i表示可选择的资产类型数,j表示剩余的可选资产zong'shu
第三步分析逻辑:如果不把 weight[i] 算入子集,那么是否能够恰好等于总和,取决于上一个状态 dp[i-1][j]。如果把 weight[i] 算入子集,那么是否能够恰好等于总和,取决于状态 dp[i - 1][j-weight[i-1]]。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int SumVal,type;
    //通过char的方式将逗号分出来,分隔出数字进行保存
    char c;
    cin >> SumVal >> c >> type >> c;
    //定义MaxVal[][]
    vector<vector<int> > MaxVal(type+1,vector<int>(SumVal+1,0));
    int weight[type];
    int val[type];
    for(int i=0;i<type;i++)
    {
        int a;
        cin>>a;
        weight[i]=a;
    }
    cin>>c;
    for(int i=0;i<type;i++)
    {
        int a;
        cin>>a;
        val[i]=a;
    }
    for(int i=1;i<=type;i++)
        for(int j=1;j<=SumVal;j++)
        {
            if(j>=weight[i-1])
            {
                MaxVal[i][j]=max(MaxVal[i-1][j],MaxVal[i-1][j-weight[i-1]]+val[i-1]);
            }
            else
                MaxVal[i][j]=MaxVal[i-1][j];
        }
    cout<<MaxVal[type][SumVal];
    return 0;
}

方法(二)递归方法

【注】效率相当低下,只能通过26%的测试用例。

我们用F(n,C)表示将前n个物品放进容量为C的背包里,得到的最大的价值。我们用自顶向下的角度来看,假如我们已经进行到了最后一步(即求解将n个物品放到背包里获得的最大价值),此时我们便有两种选择:

选择一:不放第n个物品,此时总价值为F(n−1,C)

选择二:放置第n个物品,此时总价值为vn+F(n−1,C−wn)

因此,两种选择中总价值最大的方案就是我们的最终方案,其状态转移方程为:
F(i,C)=max(F(i−1,C),v(i)+F(i−1,C−w(i)))

#include<iostream>
using namespace std;
  /**
     * 解决背包问题的递归函数
     *
     * @param w        物品的重量数组
     * @param v        物品的价值数组
     * @param index    当前待选择的物品索引
     * @param capacity 当前背包有效容量
     * @return 最大价值
     */
    int solveKS(int w[], int v[], int index, int capacity) {
        //基准条件:如果索引无效或者容量不足,直接返回当前价值0
        if (index < 0 || capacity <= 0)
            return 0;
        //不放第index个物品所得价值
        int res = solveKS(w, v, index - 1, capacity);
        //放第index个物品所得价值(前提是:第index个物品可以放得下)
        if (w[index] <= capacity) {
            res = max(res, v[index] + solveKS(w, v, index - 1, capacity - w[index]));
        }
        return res;
    }

int main(){
    int c, n, i, j;
    char m;
    cin >> c >> m >> n >> m;
    int w[n], v[n];
    for(i = 0; i < n; i++) 
        cin >> w[i];
    cin >> m;
    for(i = 0; i <n; i++) 
        cin >> v[i];
    cout<<solveKS(w, v, n - 1, c);
    return 0;
}

方法(三)记忆化搜索

【注】方法二的改进
我们用递归方法可以很简单的实现以上代码,但是有个严重的问题就是,直接采用自顶向下的递归算***导致要不止一次的解决公共子问题,因此效率是相当低下的。
我们可以将已经求得的子问题的结果保存下来,这样对子问题只会求解一次,这便是记忆化搜索。

#include<iostream>
#define Maxn 1010
using namespace std;
int memo[Maxn][Maxn];
    /**
     * 解决背包问题的递归函数
     *
     * @param w        物品的重量数组
     * @param v        物品的价值数组
     * @param index    当前待选择的物品索引
     * @param capacity 当前背包有效容量
     * @return 最大价值
     */
    int solveKS(int w[], int v[], int index, int capacity){
        //基准条件:如果索引无效或者容量不足,直接返回当前价值0
        if (index < 0 || capacity <= 0)
            return 0;

        //如果此子问题已经求解过,则直接返回上次求解的结果
        if (memo[index][capacity] != 0) {
            return memo[index][capacity];
        }

        //不放第index个物品所得价值
        int res = solveKS(w, v, index - 1, capacity);
        //放第index个物品所得价值(前提是:第index个物品可以放得下)
        if (w[index] <= capacity) {
            res = max(res, v[index] + solveKS(w, v, index - 1, capacity - w[index]));
        }
        //添加子问题的解,便于下次直接使用
        memo[index][capacity] = res;
        return res;
    }

int main(){
    int c, n, i, j;
    char m;
    cin >> c >> m >> n >> m;
    int w[n], v[n];
    for(i = 0; i < n; i++) 
        cin >> w[i];
    cin >> m;
    for(i = 0; i <n; i++) 
        cin >> v[i];
    cout<<solveKS(w, v, n - 1, c);
    return 0;
}