《暑假每日一题》Week 1: 6.20 - 6.26


《暑假每日一题》Week 1: 6.20 - 6.26


前言

  • 环境:VS 2019
  • 大一,水平不高,单纯记录暑假生活。
  • AcWing题库

Day 1 :01背包问题

关键词

  1. 每件物品只能使用一次
  2. 总体积不超过背包容量(不是正好相等哦~)
  3. 求的是总价值最大(不是最多能放多少个东西)
  4. 放进一个物品是能获得价值,不是白嫖

解题思路

  • 算法:(二维动态规划) O(n^2)

在这里插入图片描述

AC代码

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N]; //每个物品的体积
int w[N]; //每个物品的价值
int f[N][N]; //状态转移方程,上面有详细解释
int main(){
    int n, m;
    cin >> n >> m; //输入物品数量和背包容量
    for(int i = 1;i <= n;i ++) scanf("%d%d",&v[i],&w[i]); //输入每个物体的体积和价值
    for(int i = 1;i <= n;i ++){
        for(int j = 0; j <= m; j ++){
            if(j < v[i]) f[i][j] = f[i - 1][j]; //不合法,不包括i
            else f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]); //包括i
        }
    }
    cout << f[n][m] << endl; //输出答案
    return 0;
}

Day 2 : 完全背包问题

关键词

  1. 集合 : 用前i个物品 ,装进体积为j 的所有方案的集合 f[i][j]
  2. 属性 : 所选方案中物品价值的最大值
  3. 计算 | 集合的划分 : 第i个物品选择次数来划分
  4. 完全 : 每个物品可以选择无数次,但是不能超过背包的体积

解题思路

在这里插入图片描述

AC代码

朴素写法1

#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][N];
int main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
    for (int i = 1; i <= n; i ++ )
    for (int j = 1; j <= m; j ++ ){
         f[i][j] = f[i - 1][j];  // 第i个物品选0次
         for(int k = 1; k <= j / v[i]; k ++){ 
              f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]); // 选k次
         } 
      }
    cout << f[n][m] << endl;
    return 0;
    }

上面是刚开始采用了朴素写法 果然TLE了,于是下面进行了代码优化。

朴素写法2

#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][N];
int main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
    for (int i = 1; i <= n; i ++ )
    for (int j = 1; j <= m; j ++ ){
         f[i][j] = f[i - 1][j];             // 第i个物品选0次
         if (j >= v[i]) 
         f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]); //  第i个物品选其他次
         //针对朴素写法1 的f[i][j] 的等价变换 f[i][j-v]推导得出
      }
      cout << f[n][m] << endl;
      return 0;
}
          

空间优化(最终代码)

#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];
int main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
    for (int i = 1; i <= n; i ++ )
    for (int j = v[i]; j <= m; j ++ )  // 注意j的初值
         f[j] = max(f[j], f[j - v[i]] + w[i]);
         // 内循环递增  
         // 恒等式 f[j] = f[j]省略没写 ; if(j >= V[i])的条件判断 直接写到for里面 
         cout << f[m] << endl;
         return 0;
}

Day 3 :多重背包问题1

解题思路

闫氏DP分析法

一、状态表示:f[i][j]

  1. 集合:从前i个物品中选,且总体积不超过j的所有方案的集合.
  2. 属性:最大值

二、状态计算:

  1. 思想-----集合的划分
  2. 集合划分依据:根据第i个物品有多少个来划分.含0个、含1个···含k个.
    状态表示与完全背包朴素代码一样均为:
    f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
    在这里插入图片描述

AC代码

也没有想太多 暴力就完事了(嗯~暴力) 本题目n = 100, n3=1e6n3=1e6 没超时

#include <iostream>
using namespace std;
const int N = 110;
int v[N],w[N],s[N];
int n,m;
int f[N][N];
int main(){
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> v[i] >> w[i] >> s[i];
    for(int i = 1; i <= n; i++){
        for(int j = 0; j <= m; j++){
            for(int k = 0; k <= s[i] && k*v[i] <= j; k++){ // k = 0包含了 f[i-1][j]的情况
                 f[i][j] = max(f[i][j],f[i-1][j-k*v[i]] + w[i]*k);
            }
        }
    }
    cout << f[n][m] << endl;
    return 0;
}

Day 4 : 数组中数值和下标相等的元素

(摆烂的Day4)

解题思路

单调性 二分
mid左边的数会小于mid,mid右边的数会大于mid,符合单调性

AC代码

(该题选自LeetCode,所以代码提交格式不太一样)

class Solution {
public:
    int getNumberSameAsIndex(vector<int>& nums) {
        int l = 0,r = nums.size() - 1;
        while (l < r){
            int mid = l + r >> 1;
            if (mid <= nums[mid])r = mid;
            else l = mid + 1;
        }
        if (l == nums[l])
        return l;
        else 
        return -1;
    }
};

Day 5:多重背包问题ll

解题思路

因为数据加大,三重循环会超时,这时候需要用到二进制优化转为01背包

设某件物品ii 的sisi = 7 , 二进制为111111 , 20=1,21=2,22=420=1,21=2,22=4
此时我们将该物品拆分,

k = 1即当成1件物品(wi∗1,vi∗1wi∗1,vi∗1),

k = 2当成一件物品(wi∗2,vi∗2wi∗2,vi∗2),

k = 4当成一件物品(wi∗4,vi∗4wi∗4,vi∗4)

我们就可以 任意选 这被拆分得到的三件物品 凑成所有的情况,即选该物品i被选任意一个数量的情况

注意:

这些被拆分的物品,已经是像01背包一样,互不干涉的,并且只能选一次

例如:

0=0,1=1,2=2,3=1+2,4=4,5=1+4,6=2+4,7=1+2+30=0,1=1,2=2,3=1+2,4=4,5=1+4,6=2+4,7=1+2+3
再比如另一个物品i, si=11si=11, 就必须拆成k=1,k=2,k=4,k=4k=1,k=2,k=4,k=4
任意选这些被拆分得到的物品同样可以凑出所有情况, 即 0 ~ 11的所有情况

AC代码

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 12010, M = 2010;
int n, m;
int v[N], w[N];
int f[M];
int main(){
    cin >> n >> m;
    //被拆分的所有物品数量
    int cnt = 0; 
    for(int i = 1; i <= n; i ++){
        int a, b, s;
        cin >> a >> b >> s;
        int k = 1;
        while(k <= s){
            cnt ++;
            //每个被拆掉得成物品的权值
            v[cnt] = a * k;  
            w[cnt] = b * k;
            s -= k;
            k *= 2;
        }
        //11 : 1 2 4 4多出的4
        if(s > 0){
            cnt ++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
    n = cnt;
    for(int i = 1; i <= n; i ++)  //01背包
        for(int j = m; j >= v[i]; j--){
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
        cout << f[m] << endl;
        return 0;
}

Day 6 : 混合背包问题

解题思路

完全背包虽然说我们可以选的个数是无穷个,但是因为有体积限制所以我们完全背包实际上也是有限的
因此完全背包可以看作每个物品可以选择的个数为m(总体积)/vi[i]的一个多重背包
然而多重背包可以转换为等价的01背包
所以这题的三个背包都可以转化为01背包
所以整个问题可以转化为为一个01背包问题

AC代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, V, w[N], v[N], cnt, dp[N];
int main(){
    cin >> n >> V;
    for (int i = 1; i <= n; i++){
        int a, b, s;
        cin >> a >> b >> s;
        if (s < 0) s = 1;//其实就是s==-1的情况,只能用一次,于是我们把它转化为数量为1的多重背包.
        if (s == 0) s = V / a;//完全背包,可以放的东西总体积不超过V,于是可以转化为数量为V/a(C++中int变量相除向下取整)的多重背包.
        int k = 1;
        while (k <= s){//常规多重背包处理,用二进制优化
            cnt ++;
            v[cnt] = k * a;
            w[cnt] = k * b;
            s -= k;
            k *= 2;
        }
        if (s > 0)
        {
            cnt ++;
            v[cnt] = s * a;
            w[cnt] = s * b;
        }
    }
    for (int i = 1; i <= cnt; i++)//01背包处理
    {
        for (int j = V; j >= v[i]; j--)
        {
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
        }
    }
    cout << dp[V] << endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值