「CodeNote」 HDU3466

Posted by Dawn-K's Blog on August 6, 2019

HDU3466

[TOC]

中文题意

给出n个商品,以及自己的钱数m,然后以下n行,每一行给出p,q,v三个数,表示第i个商品的价格,以及最少需要多少钱才能进行购买,和收益

求最大价值

思路

根据HDU2546(饭卡)的经验,我们也不难发现这个题的产品应该也是需要排序的,但是每个商品有两个变量,这个就带来了排序的困难.

如果仅凭直觉,到底是以哪个参数排序为主,哪个参数排序为辅呢?似乎都能举出反例..网上有大神指出,可以这样想: 如果有两个商品A和B,要求分别是p1,q1,p2,q2然后我们手动模拟一下对于这两个数的抉择.比如我们先选A再选B,则初始余额至少需要p1+q2;如果先选B再选A,则初始余额至少要p2+q1.然后我们可以理解为取得相同的收益,成本自然越少越好.所以我们要选取p1+q2和p2和q1中最小的一个,排序函数也就是

1
2
3
bool cmp(B &x,B &y){
    return x.p+y.q>x.q+y.p;
}

为了便于书写,我们将其化成

1
2
3
bool cmp(B &x,B &y){
    return x.q-x.p < y.q-y.p;
}

这样就得到了正解

我们再看一下从数学角度的分析

我们根据HDU2546给出的思路,我们发现状态转移的函数是这样的

1
2
3
4
5
6
for (int i = 1; i <= n; ++i) {
    // 题中说了 p<=q
    for (int j = m; j >= b[i].q; --j) {
        dp[j] = max(dp[j], dp[j - b[i].p] + b[i].v);
    }
}

和01背包没有区别,但是我们分析其查询的数组的范围,我们发现,查询的范围是[m,b[i].q-b[i].p] 也就是q-p越小,其范围越大,所以我们应该让q-p越小的数排在越前面.

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<iostream>
#include<cstring>
#include<string>
#include <algorithm>
#include <iomanip>
// 参考资料 http://www.mamicode.com/info-detail-1009053.html
// https://www.cnblogs.com/kuangbin/archive/2012/09/14/2685826.html
// 至于按照q-p从小到大排序比较难想到。q-p其实就是不更新的范围,不更新的范围从小到大递增时就不会影响后面的DP了。(从dp转移方程的正确性角度分析
// 一种组合的排序策略--限制又小价格又贵的先选,也就是q-p小的先选。为什么这样呢?A:p1,q1 B: p2,q2,先选A,则至少需要p1+q2的容量,而先选B则至少需要p2+q1,如果p1+q2>p2+q1,那么要选两个的话的就要先选A再选B,公式可换成q1-p1 > q2-p2,就按这样的方法排序最后的顺序就是最优的顺序.(从逻辑角度分析前后顺序分析

using namespace std;
struct B {
    int p, q, v;
} b[550];

// 神仙
bool cmp(B &x, B &y) {
    return x.q - x.p < y.q - y.p;
}

int dp[5500];

int main() {
    int n, m;
    while (cin >> n >> m) {
        for (int i = 1; i <= n; ++i) {
            cin >> b[i].p >> b[i].q >> b[i].v;
        }
        sort(b + 1, b + n + 1, cmp);
//        for (int i = 1; i <= n; ++i) {
//            cerr << b[i].p << " " << b[i].q << " " << b[i].v << endl;
//        }
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n; ++i) {
            // 题中说了 p<=q
            for (int j = m; j >= b[i].q; --j) {
                dp[j] = max(dp[j], dp[j - b[i].p] + b[i].v);
            }
        }
        cout << dp[m] << endl;
    }

    return 0;
}

启示

面对需要考虑先后顺序的01背包问题,我们应该由数学角度进行排序,以让dp数组不错过最优解