首页 > 其他分享 >背包问题(01背包与完全背包)

背包问题(01背包与完全背包)

时间:2024-05-31 22:44:40浏览次数:21  
标签:邮票 背包 int max 样例 完全 01 include

dp考虑两个方面,包括如何表示状态(维度,属性(min、max、cnt)),如何计算当前状态(状态转移方程)。dp问题的优化一般是对状态转移方程进行等价变形。

01背包问题

有n个物品和一个容量为V的背包。

每个物品有两个属性,包括所占用的体积v以及拥有的价值w,每件物品只能用一次。

求背包能装得下的情况下所能拥有的最大价值为多少。

f[i, j] = max(f[i-1, j], f[i, j-v] + w

滚动数组优化

  • 能发现,f[i][j] 的状态转移仅使用到了 f[i-1][...],故可以采用滚动数组来做。即当前层的状态转移仅与上一层有关
  • 当前层是 i & 1,上一层是 i-1 & 1

完全背包问题

与01背包的区别在于,每件物品可以拿无数次。

优化过程

f[i, j] = max(f[i-1, j], f[i-1, j-v]+w, f[i-1, j-2v]+2w, f[i-1, j-3v]+3w ...)
对比:
f[i, j-v] = max(f[i-1, j-v], f[i-1, j-2v]+w, f[i-1, j-3v]+2w ...)

也就是

f[i][j]    =max(f[i-1][j], f[i-1][j-v]+w, f[i-1][j-2v]+2w, f[i-1][j-3v]+3w,...)
f[i][j-v]  =max(           f[i-1][j-v], f[i-1][j-2v]+w, f[i-1][j-3v]+2w, f[i-1][j-4v]+3w,...)
f[i][j-v]+w=max(           f[i-1][j-v]+w, f[i-1][j-2v]+2w, f[i-1][j-3v]+3w, f[i-1][j-4v]+4w,...)

1)通过两项对比,可以得出结论,可省掉一重循环

f[i, j] = max(f[i][j], f[i][j-v]+w)

所以实际上多重背包的本质也还是01背包

但是01背包问题为从后往前面推导,完全背包问题为从前往后推导

2)同时,对状态存储进行优化,还可以省略掉一维数组

f[j] = max(f[j], f[j-v]+ w)

3)完全背包问题实际上求得的是某一个数组区间内的最大值,也可以视作滑动窗口最大值,对于该问题即可利用单调队列进行优化

习题讲解

1. 最长上升子序列

题目描述

这是一个简单的动规板子题。

给出一个由 \(n(n\le 5000)\) 个不超过 \(10^6\) 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。

最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。

输入格式

第一行,一个整数 \(n\),表示序列长度。

第二行有 \(n\) 个整数,表示这个序列。

输出格式

一个整数表示答案。

样例输入 #1

6
1 2 4 1 3 4

样例输出 #1

4

提示

分别取出 \(1\)、\(2\)、\(3\)、\(4\) 即可。

代码参考

//B3637
#include<stdio.h>
#include<iostream>
using namespace std;
int a[5005], f[5005];

int main(){
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        f[i] = 1;
    }
    for(int i = 2; i <= n; i++){
        for(int j = 1; j <= i-1; j++){
            if(a[i] > a[j])     f[i] = max(f[i], f[j] + 1);
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; i++){
        ans = max(ans, f[i]);
    }
    cout << ans << endl;
    return 0;
}

2. NASA的食物计划

题目背景

NASA(美国航空航天局)因为航天飞机的隔热瓦等其他安全技术问题一直大伤脑筋,因此在各方压力下终止了航天飞机的历史,但是此类事情会不会在以后发生,谁也无法保证。所以,在遇到这类航天问题时,也许只能让航天员出仓维修。但是过多的维修会消耗航天员大量的能量,因此 NASA 便想设计一种食品方案,使体积和承重有限的条件下多装载一些高卡路里的食物。

题目描述

航天飞机的体积有限,当然如果载过重的物品,燃料会浪费很多钱,每件食品都有各自的体积、质量以及所含卡路里。在告诉你体积和质量的最大值的情况下,请输出能达到的食品方案所含卡路里的最大值,当然每个食品只能使用一次。

输入格式

第一行 \(2\) 个整数,分别代表体积最大值 \(h\) 和质量最大值 \(t\)。

第二行 \(1\) 个整数代表食品总数 \(n\)。

接下来 \(n\) 行每行 \(3\) 个数 体积 \(h_i\),质量 \(t_i\),所含卡路里 \(k_i\)。

输出格式

一个数,表示所能达到的最大卡路里(int 范围内)

样例 #1

样例输入 #1

320 350
4
160 40 120
80 110 240
220 70 310
40 400 220

样例输出 #1

550

提示

对于 \(100\%\) 的数据,\(h,t,h_i,t_i \le 400\),\(n \le 50\),\(k_i \le 500\)。

代码参考

#include <stdio.h>
#include <iostream>
using namespace std;
int v[55], m[55], kolo[55];
//dp数组
int f[55][405][405];

int main(){
    int v_max, m_max;
    int N;
    int t1, t2;
    cin >> v_max >> m_max;
    cin >> N;
    for(int i = 1; i <= N; i++)
        cin >> v[i] >> m[i] >> kolo[i];
    for(int i = 1; i <= N; i++){
        for(int j = 0; j <= v_max; j++)
            for(int k = 0; k <= m_max; k++){
                t1 = v[i];
                t2 = m[i];
                f[i][j][k] = f[i-1][j][k];
                if(j >= t1 && k >= t2)
                f[i][j][k] = max(f[i-1][j][k], f[i-1][j-t1][k-t2]+kolo[i]);
            }
    }
    cout << f[N][v_max][m_max];
    return 0;
}

3. [NOIP2006 普及组] 开心的金明

题目描述

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 \(N\) 元钱就行”。今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的 \(N\) 元。于是,他把每件物品规定了一个重要度,分为 \(5\) 等:用整数 \(1-5\) 表示,第 \(5\) 等最重要。他还从因特网上查到了每件物品的价格(都是整数元)。他希望在不超过 \(N\) 元(可以等于 \(N\) 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。

设第\(j\)件物品的价格为 \(v_j\),重要度为 \(w_j\),共选中了 \(k\) 件物品,编号依次为 \(j_1,j_2,…,j_k\),则所求的总和为:

\(v_{j_1} \times w_{j_1}+v_{j_2} \times w_{j_2} …+v_{j_k} \times w_{j_k}\)。

请你帮助金明设计一个满足要求的购物单。

输入格式

第一行,为 \(2\) 个正整数,用一个空格隔开:\(n,m\)(\(n<30000,m<25\))其中 \(n\) 表示总钱数,\(m\) 为希望购买物品的个数。

从第 \(2\) 行到第 \(m+1\) 行,第 \(j\) 行给出了编号为 \(j-1\) 的物品的基本数据,每行有 \(2\) 个非负整数 \(v,p\)(其中 \(v\) 表示该物品的价格 \((v \le 10000)\),\(p\) 表示该物品的重要度(\(1\le p\le5\))。

输出格式

\(1\) 个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(\(<100000000\))。

样例 #1

样例输入 #1

1000 5
800 2
400 5
300 5
400 3
200 2

样例输出 #1

3900

提示

NOIP 2006 普及组 第二题

代码参考

//P2725
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
//f[i][j]数组代表当选到第i个物品,花了j元时,所能得到的价格与权重的乘积和最大值
int f[30][30005];
struct wp{
    int v, p;
}w[30];

int main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= m; i++)     cin >> w[i].v >> w[i].p;
    for(int i = 1; i <= m; i++){
        for(int j = 1; j <= n; j++){
            if(j >= w[i].v)     f[i][j] = max(f[i-1][j], f[i-1][j-w[i].v] + w[i].v * w[i].p);
            else    f[i][j] = f[i-1][j];
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; i++)
        ans = max(ans, f[m][i]);
    cout << ans << endl;
    return 0;
}

4. [USACO3.1] 邮票 Stamps

题目描述

给一组 \(n\) 枚邮票的面值集合和一个上限 \(k\) —— 表示信封上能够贴 \(k\) 张邮票。请求出最大的正整数 \(m\),满足 \(1\) 到 \(m\) 的面值都可以用不超过 \(k\) 张邮票表示出来。

输入格式

输入的第一行是两个整数,分别代表邮票上限 \(k\) 和邮票面值数 \(n\)。

自第二行起,除最后一行外,每行有 \(15\) 个整数 \(a_i\) ,最后一行的整数个数不超过 \(15\),共有 \(n\) 个整数,第 \(i\) 个整数代表第 \(i\) 种邮票的面值 \(a_i\)。

输出格式

输出一行一个整数代表 \(m\)。若 \(m\) 不存在请输出 \(0\)。

样例 #1

样例输入 #1

5 2
1 3

样例输出 #1

13

提示

样例输入输出 1 解释

有 \(1\) 分和 \(3\) 分的邮票;你最多可以贴 \(5\) 张邮票。很容易贴出 \(1\) 到 \(5\) 分的邮资(用 \(1\) 分邮票贴就行了),接下来的邮资也不难:

  • \(6 = 3 + 3\)。
  • \(7 = 3 + 3 + 1\)。
  • $8 = 3 + 3 + 1 + 1 $。
  • $9 = 3 + 3 + 3 $。
  • $10 = 3 + 3 + 3 + 1 $。
  • $11 = 3 + 3 + 3 + 1 + 1 $。
  • $12 = 3 + 3 + 3 + 3 $。
  • \(13 = 3 + 3 + 3 + 3 + 1\)。

然而,使用 \(5\) 枚 \(1\) 分或者 \(3\) 分的邮票根本不可能贴出 \(14\) 分的邮资。因此,答案为 \(13\)。

数据规模与约定

对于 \(100\%\) 的数据,保证 \(1 \leq k \leq 200\),\(1 \leq n \leq 50\),\(1 \leq a_i \leq 10^4\)。

说明

题目翻译来自 NOCOW。

代码参考

//P2725
//完全背包
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
int a[55];
//f[i]记录面值为i时最少需要的邮票数
int f[2000005];

int main(){
    int n, k;
    cin >> k >> n;
    memset(f, 0x3f3f3f,sizeof(f));
    f[0] = 0;
    for(int i = 1; i <= n; i++)     cin >> a[i];
    for(int i = 1; i <= 2000000; i++){
        for(int j = 1; j <= n; j++){
            if(i >= a[j])     f[i] = min(f[i-a[j]]+1, f[i]);
        }
    }
    for(int i = 1; i <= 2000000; i++){
        if(f[i] > k){
            cout << i-1 << endl;
            return 0;
        }
    }
    return 0;
}

5.机器分配

题目描述

总公司拥有高效设备 \(M\) 台,准备分给下属的 \(N\) 个分公司。各分公司若获得这些设备,可以为国家提供一定的盈利。问:如何分配这 \(M\) 台设备才能使国家得到的盈利最大?求出最大盈利值。其中 \(M \le 15\),\(N \le 10\)。分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数 \(M\)。

输入格式

第一行有两个数,第一个数是分公司数 \(N\),第二个数是设备台数 \(M\)。

接下来是一个 \(N \times M\) 的矩阵,表明了第 \(i\) 个公司分配 \(j\) 台机器的盈利。

输出格式

第一行为最大盈利值。

接下来 \(N\) 行为第 \(i\) 分公司分 \(x\) 台。

P.S. 要求答案的字典序最小。

样例 #1

样例输入 #1

3 3
30 40 50
20 30 50
20 25 30

样例输出 #1

70
1 1
2 1
3 1

代码参考

//P2066
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
typedef long long ll;
ll a[15][20];
//f[i][j]代表取到第i家公司,用掉了j台设备时,能够获得的利益最大值
ll f[15][20];
//存答案
ll cnt[15][20][2];
ll cnt2[15];

int main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            cin >> a[i][j];
        }
    }
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            for(int k = 0; k <= j; k++){
                if(f[i][j] <= f[i-1][j-k] + a[i][k]){
                    f[i][j] = f[i-1][j-k] + a[i][k];
                    cnt[i][j][0] = k;
                    cnt[i][j][1] = j-k;
                }
            }
        }
    }
    //输出最大值
    ll ans = 0, t = 0;
    for(int i = 1; i <= m; i++){
        if(ans <= f[n][i]){
            t = i;
            ans = f[n][i];
        }
    }
    cout << ans << endl;
    //输出每一项数量
    for(int i = n; i >= 1; i--){
        cnt2[i] = cnt[i][t][0];
        t = cnt[i][t][1];
    }
    for(int i = 1; i <= n; i++)     cout << i << " " << cnt2[i] << endl;
    return 0;
}

标签:邮票,背包,int,max,样例,完全,01,include
From: https://www.cnblogs.com/hsy2093/p/18225375

相关文章

  • 【教学类-60-01】彩色消划掉01(四个数字,X*Y宫格)
    背景需求:......
  • L2-013 红色警报(并查集)
    1.题目L2-013红色警报分数25全屏浏览切换布局作者陈越单位浙江大学战争中保持各个城市间的连通性非常重要。本题要求你编写一个报警程序,当失去一个城市导致国家被分裂为多个无法连通的区域时,就发出红色警报。注意:若该国本来就不完全连通,是分裂的k个区域,而失去一个城......
  • 复习day01
    md学习1.标题各级标题为#个数+空格+名字2.字体两侧单*为斜字体双*为粗体三*为斜粗~为删除线3.引用直接>加引用内容4.分割线3个-或者3个*5.图片!+[放图片名]+(图片路径)6.超链接[超链接名字]+(网址)LOL官网(md不支持打开链接)引用博客时使用7.表格ctrl+t设......
  • 【LeetCode算法】第101题:对称二叉树
    目录一、题目描述二、初次解答三、官方解法四、总结一、题目描述二、初次解答1.思路:递归判定左子树和右子树是否对称。用一个新函数sym来递归判定左子树和右子树是否对称。该函数细节:判定当前传入的两个根节点是否为空,若均为空则返回true,若只有其中一个为空则返回fa......
  • P6239 [JXOI2012] 奇怪的道路
    P6239[JXOI2012]奇怪的道路状压dp题目的限制可以把图拍成一个序列,在序列上考虑连边。求方案数,考虑dp。观察到\(k\)的大小、每个位置只有奇偶性和边数限制,可以设\(f_{i,j,s}\)表示考虑完前\(i\)个点,连了\(j\)条边,\([1,i]\)的度数状态为二进制\(s\),此时\([1,i-k]\)......
  • CSP历年复赛题-P1980 [NOIP2013 普及组] 计数问题
    原题链接:https://www.luogu.com.cn/record/160821231题意解读:统计1~n中x的个数。解题思路:枚举每个数,提取每一位,判断是否等于x。100分代码:#include<bits/stdc++.h>usingnamespacestd;intn,x,ans;intmain(){cin>>n>>x;for(inti=1;i<=n;i++)......
  • CSP历年复赛题-P1078 [NOIP2012 普及组] 文化之旅
    原题链接:https://www.luogu.com.cn/problem/P1078题意解读:1~n个国家,每个国家有自己的文化,不同国家文化可以相同,要从起点遍历到终点,已经学习过的文化不能重复学习,已经学习过的文化被某个文化歧视的国家也不能遍历,且不同国家之间有边,边有不同的距离,计算从起点到终点的最短路径。解......
  • 01Linux以及操作系统概述
    课程目标1.了解现代操作系统的整体构成及发展历史2.了解Linux操作系统及其分支版本3.直观上理解服务器端与桌面端版本的区别课程实验1.通过对CentOS和Ubuntu的演示,直观理解Linux与Windows的异同课堂引入本章内容主要为大家详细讲解Linux操作系统(以下简称Linux)的基本情......
  • [JS零碎知识点01]Math内置对象
    一:Math数学对象Math 是一个内置对象,它拥有一些数学常数属性和数学函数方法。二:random()方法1 random()简介Math.random()为随机数函数,返回一个0到1之间随机小数数,并且包括0不包括1,[0,1)2随机数生成算法(1)生成0-10之间的随机整数Math.floor(Math.random()*11)推倒:Math......
  • 浅析背包问题
    理解递推式(或动态规划转移方程)是解决动态规划问题的关键。如果你对这类问题不太理解,下面我将通过一个简化的例子和逐步解释来帮助你理解如何构建和使用递推式。例子:0/1背包问题问题描述给定一个容量为W的背包和n个物品,每个物品有一个重量w[i]和一个价值v[i]。求......