首页 > 其他分享 >笔记:洛谷 P3985 不开心的金明

笔记:洛谷 P3985 不开心的金明

时间:2023-04-01 16:58:23浏览次数:67  
标签:状态 le 洛谷 P3985 商品 minp dp 金明

算法笔记: [背包问题] 洛谷P3985 不开心的金明

题目详情

原题链接:洛谷P3985 不开心的金明

不开心的金明

Description

  金明今天很不开心,家里购置的二手房就要领钥匙了,房里并没有一间他自己专用的很宽敞的房间。更让他不高兴的是,妈妈昨天对他说:“你需要购买哪些物品,怎么布置,你说了不算(有很大的限制),而且不超过W元钱。”。今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的W元。于是,他把每件物品规定了一个重要度整数\(p_i\)表示。他还从因特网上查到了每件物品的价格\(v_i\)(都是整数元)。
  妈妈看到购物单后进行了审查,要求购物单上所有的物品价格的极差(最贵的减去最便宜的)不超过3(当然金明至今不知道为什么会这样)。他希望在不超过W元(可以等于W元)的前提下,使购买的重要度总和\(\sum p_i\)的最大。
  请你帮助金明设计一个满足要求的购物单,你只需要告诉我们重要度的最大的和。

Input Format

  输入的第1行,为两个正整数,用一个空格隔开:
n W (其中W表示总钱数,n为希望购买物品的个数。)

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

Output Format

  输出只有一个正整数,为不超过总钱数的物品的重要度的总和的最大值

I/O Sample #1

Sample Input #1

5 10
2 800
5 400
5 300
3 400
2 200

Sample Output #1

1600

Hint

\(1 \le N \le 100\)
\(1 \le W \le 10^9\)
\(1 \le vi \le 10^9\)

对所有的 \(i=1,2,3,…,N\),$ min(v_i) \le v_i \le min(v_i)+3$.
\(1 \le p_i \le 10^7\)

疑惑的来源

  起初我只是把它当作简单的背包问题,但是观察了数据规模后发现商品的单价\(v_i\)和限制的总价格\(w\)过于庞大,以至于无法开出这么大规模的数组dp[w],所以解法势必根据数据规模进行优化。而我发现有以下两种解法:

解法一 优化价格 使用三维数组dp[i][w][k](恰当的解法)

优化价格

  找出商品中的最小价格\(minp\), 然后将每个价格减去最小价格:\(p_i-minp\), 由于题目限制商品价格的极差不大于\(3\),那么这样处理过后的商品价格只有\(p`_i=0,1,2,3\)这几种情况,同时商品总价\(w\)也被压缩成\(sumv = \sum_{i = 1}^{n} p'_i\).

使用三维数组dp[i][w][k]

首先描述一下数组dp各个维度的意义
  • i表示遍历到第i个商品时的状态
    对于遍历到的每一个物品,金明都可以选择买或者不买
  • w表示当前的预算大小
    相当于子背包或者子问题,最终问题的答案(即在金明妈妈给出的预算内怎么买商品)由较小预算的状态推演过来.
  • k表示购买的商品数量
    例如当dp[5][10][4]表示金明希望购买5件商品,但妈妈只给出10块钱预算的情况下,买4件商品的最优方案.(可能不存在)
如何遍历三维数组dp[][][]

直接上代码

// 从清单中逐一挑选商品
for (int i = 1; i <= n; i ++)
    /*
        由于我们不知道优化后的总预算是多少,
        所以这里暂且用优化后的商品总价代替.
    */
    for (int j = sumv; j >= p[i]; j --)
        // 购买数量亦是状态里的一部分
        for (int k = n; k >= 1; k --)
            // 确保不超出预算则更新状态
            if ((long long) minv * k + j <= w)
                // 在此处更新dp的状态
                dp[i][j][k] = max(dp[i - 1][j][k], dp[i][j - p[i]][k - 1] + c[i]);
            else
                // 没得选择
                dp[i][j][k] = dp[i - 1][j][k];
然后就是状态转移方程
dp[i][j][k] = max(dp[i - 1][j][k], dp[i][j - p[i]][k - 1] + c[i]);

状态方程的具体意思是,当前dp[i][j][k]的取值由买和不买决定:
如果买,则取值为上一个商品i - 1的各种状态中,花费j - p[i],共购买了k件商品的状态,由该状态下的重要度加上当前商品的重要度,即dp[i][j - p[i]][k - 1] + c[i]为取值;
如果不买或者超出总预算(else中的语句),则直接继承上一个商品i - 1同维度下的状态即可.
总之,可买可不买取其大者,买不了则继承之前的状态.

其实还有优化的余地:滚动二维数组

以下是优化后的代码

for (int j = sumv; j >= p[i]; j --)
    for (int k = n; k >= 1; k --)
        if ((long long) minv * k + j <= w)
            dp[j][k] = max(dp[j][k], dp[j - p[i]][k - 1] + c[i]);

优化后尤其要注意状态维度的遍历顺序,因为维度i被优化掉了,那么更新状态时,数组dp[][]中同时存在着新旧状态,新状态需要由旧状态中的小者更新而来,所以我们倒序遍历各个状态维度,即可保证还未被转移的旧小状态,不会被新小状态替代.

解法二 01背包 + 贪心(不太恰当的解法)

  根据数据规模的大小,分别采用两种方法,当规模较小时,按照普通的01背包问题解决;当规模较大时,使用贪心算法,题解中给出的理由是:当商品的最小价格\(minp\)达到阈值时,将预算统统购买最便宜或最贵的商品,两者可购买的数量是相等的.
  对于一维数组dp的规模极限,由于商品价格极差不大于3,所以可能有\(\left \lfloor \frac{W}{minp} \right \rfloor =\left \lfloor \frac{W}{minp+3} \right \rfloor ​≤100\).当上述情况成立时,题解认为可以使用贪心.将商品清单按重要度排序,自大而小取走即可,并且题解推导出当\(minp > 300\)时,上述情况成立.

勘误

勘误推理参考:君义_noip的证明

实际上\(minp > 300\)只是\(\left \lfloor \frac{W}{minp} \right \rfloor =\left \lfloor \frac{W}{minp+3} \right \rfloor ​≤100\)的必要不充分条件
当\(W\)被\(minp\)整除时,\(\left \lfloor \frac{W}{minp} \right \rfloor =\left \lfloor \frac{W}{minp+3} \right \rfloor ​≤100\)就已经不成立了,此时使用贪心得到的结果可能是错误的.
例如以下数据

800 3

400 3
400 4
401 5

此时贪心给出的答案为5, 而正确答案为7

标签:状态,le,洛谷,P3985,商品,minp,dp,金明
From: https://www.cnblogs.com/yousa/p/17278869.html

相关文章

  • prim算法(洛谷P1547)
    P1547[USACO05MAR]OutofHayS模板/*B1682[Usaco2005Mar]OutofHay干草危机洛谷P1547[USACO05MAR]OutofHayS====关键词===================================================prim算法(最小生成树)1.WA,没有加重复边的判断2.加了重复边的判断,还是WA,但分数高了一点3......
  • 洛谷P1908 逆序对
    题目描述猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中ai​>aj​且 i<j 的有序对。......
  • 洛谷P3374 【模板】树状数组 1-(单点修改,区间查询)
    题目描述如题,已知一个数列,你需要进行下面两种操作:将某一个数加上 x求出某区间每一个数的和输入格式第一行包含两个正整数 n,m,分别表示该数列数字的个数和操作的总个数。第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。接下来 ......
  • 洛谷P3368 【模板】树状数组 2-(区间修改,单点查询)
    题目描述如题,已知一个数列,你需要进行下面两种操作:将某区间每一个数加上 x;求出某一个数的值。输入格式第一行包含两个整数 N、M,分别表示该数列数字的个数和操作的总个数。第二行包含 N 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。接下来......
  • 洛谷 P5642 - 人造情感(换根 dp)
    想起来很轻松,写起来很酸爽的套路题。默认以\(1\)为根。先考虑怎么算单个\(f(u,v)\),我们定义一个连通块的权值为从该连通块中选出若干条点不相交的路径,选出的路径的权值之和的最大值。那么显然\(f(u,v)\)就是整棵树的权值\(-\)挖掉\((u,v)\)这条路径后各个连通块的权值之......
  • 洛谷9150题解
    考虑把\(i\tok_i\)连边,这样形成若干个环。考虑断环为链并且把链复制一份接到后面。考虑求出从一个点集开始拓展能够到达的点集\(S1_i\)。显然\(S1_i\)在环上是连续的,设\(r_i\)表示第\(i\)个节点拓展能得到的右端点。考虑每个节点\(i\)所在强连通分量的点集合\(S2\)。可以证明\(......
  • 洛谷P1036 选数
    1#include<bits/stdc++.h>2usingnamespacestd;3intn,k,a[25];4inthk;//这个是k个数加起来的和5intsum;//这个是个数(输出的那个)6intpdzhi(intx){......
  • 洛谷P1020
    P1020[NOIP1999普及组]导弹拦截思考这题很显然是一道DP题,第一问就是求该序列的最长不下降子序列.先说最长不下降子序列的\(O(n^2)\)的做法.用\(dp_k\)表示第\(k\)......
  • 数组模拟双向列表 洛谷 P1160 队列安排
    题目描述一个学校里老师要将班上N个同学排成一列,同学被编号为1~N,他采取如下的方法:1.先将1号同学安排进队列,这时队列中只有他一个人;2.2~N号同学依次入列,编号为i的同学入列方式......
  • 洛谷 P1967 货车运输
    P1967NOIP2013提高组]货车运输-洛谷|计算机科学教育新生态(luogu.com.cn)这个题目算是lca的稍微拓展吧。主要思考方向应该是很明显的。就是考虑一条路径上权值最......