复赛四:推销员

洛谷::P2672
OJ:P4949

方法一:暴力搜索(40分)

解题策略:

为了在不走多余路的前提下最大化疲劳值,阿明应选择最远的住户 j 作为终点,并在返回的路上尽可能多地推销给距离较近且疲劳值高的住户。

在到达最远的住户 j 的过程中,他只能访问 j 之前的住户,因此需要从这些住户中选择 (X - 1) 个疲劳值最大的住户进行推销。

总疲劳值由以下三部分组成:

  1. 到最远住户 j 的往返距离产生的疲劳值:2 * a[j]
  2. 在最远住户 j 处推销产生的疲劳值:b[j]
  3. 在返回路上推销给其他 (X - 1) 个住户产生的疲劳值:从 c[] 中选取的 (X - 1) 个最大 b[k] 值的总和。

通过枚举所有可能的最远住户 j,并对每种情况计算最大疲劳值,最终选取最大的疲劳值作为结果。

代码实现:

 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
45
46
47
48
49
50
/**************************************************************** 
 * Description: 2015年普及组复赛第四题 推销员  40分
 * Author: Alex Li
 * Date: 2023-10-31 11:40:18
 * LastEditTime: 2024-09-19 10:54:05
****************************************************************/
#include <iostream>
#include <algorithm>
using namespace std;
int n;
int a[100005], b[100005], c[100005];

int main() {
    cin >> n; // 输入住户数量

    // 读取每个住户到入口的距离 a[i]
    for (int i = 0; i < n; i++) cin >> a[i];

    // 读取每个住户对应的推销疲劳值 b[i]
    for (int i = 0; i < n; i++) cin >> b[i];

    // 对于不同的 X(要推销的住户数量)
    for (int X = 1; X <= n; X++) {
        int sum = 0; // 用于存储当前 X 下的最大疲劳值

        // 尝试所有可能的最远访问的住户 j(从 X-1 开始,保证有足够的住户数量)
        for (int j = X - 1; j < n; j++) {
            int maxn = 0; // 当前方案的疲劳值

            // 将从入口到第 j-1 个住户的疲劳值复制到数组 c 中
            for (int k = 0; k < j; k++) c[k] = b[k];

            // 对数组 c 进行排序,以便选择最大的 (X - 1) 个疲劳值
            sort(c, c + j);

            // 选取最大的 (X - 1) 个疲劳值,累加到 maxn 中
            for (int k = j - 1; k >= j - X + 1; k--)
                if (k >= 0) maxn += c[k];

            // 加上到第 j 个住户的来回距离所产生的疲劳值和推销疲劳值
            maxn += 2 * a[j] + b[j];

            // 更新当前 X 下的最大疲劳值
            sum = max(sum, maxn);
        }

        // 输出对于当前 X 的最大疲劳值
        cout << sum << endl;
    }
}

方法二:100分

解题策略:从下面两种方案中选最大的

  • 方案一: 选择前 i 个疲劳值最大的住户,可能这些住户的距离并不远,但累积的疲劳值高。
    • 总疲劳值包括:
    • 推销疲劳值总和:pre[i]
    • 最大往返距离产生的疲劳值:preMax[i]
  • 方案二: 选择前 i - 1 个疲劳值最大的住户,再从剩余住户中选择一个 距离+疲劳值(2 * d + v) 最大的住户。
    • 这样可能增加总的往返距离和疲劳值,使总疲劳值更大。
  • 为什么需要两种方案取最大值:
  • 有时候,选择距离较远但疲劳值高的住户,可以增加总疲劳值,即使需要放弃一个疲劳值较低的住户。
  • 两种方案分别考虑了:
    • 方案一: 最大化推销疲劳值。
    • 方案二: 平衡推销疲劳值和往返距离的疲劳值。

代码实现:

 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
45
46
47
48
49
50
51
52
53
54
55
56
/**************************************************************** 
 * Description: 2015年普及组复赛第4题,推销员 解法二
 * Author: Alex Li
 * Date: 2023-11-01 10:41:55
 * LastEditTime: 2024-09-19 11:50:04
****************************************************************/
#include <iostream>
#include <algorithm> // 引入算法库,用于 sort 函数
using namespace std;

struct node {
    int d;  // 距离 (distance to the entrance)
    int v;  // 疲劳值 (fatigue value from selling to this household)
} a[100005];

int pre[100005];    // 前缀和数组,表示按疲劳值排序后,前 i 个疲劳值之和
int last[100005];   // 后缀最大值数组,表示从第 i 个到第 n 个中,2*d + v 的最大值
int preMax[100005]; // 前缀最大值数组,表示前 i 个中,2*d 的最大值(即最大往返距离)
int n;              // 住户数量

// 比较函数,用于按疲劳值从大到小排序
bool cmp(node x, node y) {
    return x.v > y.v;
}

int main() {
    cin >> n; // 输入住户数量
    // 读取每个住户到入口的距离
    for (int i = 1; i <= n; i++) cin >> a[i].d;
    // 读取向每个住户推销产品所积累的疲劳值
    for (int i = 1; i <= n; i++) cin >> a[i].v;

    // 按疲劳值从大到小排序
    sort(a + 1, a + 1 + n, cmp);

    // 计算前缀和 pre[i],表示前 i 个最大的疲劳值之和
    for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + a[i].v;

    // 计算 preMax[i],表示前 i 个中,2*d 的最大值(最大往返距离)
    for (int i = 1; i <= n; i++)
        preMax[i] = max(preMax[i - 1], 2 * a[i].d);

    // 计算 last[i],表示从第 i 个到第 n 个中,2*d + v 的最大值
    // 这是为了考虑可能选择一个更远的住户,其往返距离和疲劳值的总和更大
    for (int i = n; i >= 1; i--)
        last[i] = max(last[i + 1], 2 * a[i].d + a[i].v);

    // 对于每个 X(从 1 到 n),计算阿明能够积累的最大疲劳值
    for (int i = 1; i <= n; i++)
        // 两种方案取最大值输出
        // 方案一:选择前 i 个最大的疲劳值住户,往返距离为其中最大的 2*d
        // 总疲劳值为:pre[i] + preMax[i]
        // 方案二:选择前 i-1 个最大的疲劳值住户,另选一个剩余住户,使 2*d + v 最大
        // 总疲劳值为:pre[i - 1] + last[i]
        cout << max(pre[i] + preMax[i], pre[i - 1] + last[i]) << endl;
}                                                                           
Scroll to Top