题解:洛谷 P3650 滑雪课程设计

【题目来源】

洛谷:P3650 [USACO1.3] 滑雪课程设计Ski Course Design - 洛谷

【题目描述】

农民约翰的农场里有 n 座山峰,每座山都有一个在 0 到 100 之间的整数的海拔高度。在冬天,因为山上有丰富的积雪,约翰经常开办滑雪训练营。

不幸的是,约翰刚刚得知税法在滑雪训练营方面有新变化,明年开始实施。在仔细阅读法律后,他发现如果滑雪训练营的最高和最低的山峰海拔高度差大于 17 就要收税。因此,如果他改变山峰的高度(使最高与最低的山峰海拔高度差不超过 17 ),约翰可以避免支付税收。

如果改变一座山 x 单位的高度成本是 x^ 2 单位,约翰最少需要付多少钱才能使海拔最高的山峰与海拔最低的山峰的高度只差不超过 17 约翰只愿意改变整数单位的高度。

【输入】

输入的第一行是一个整数,代表山峰的数量 n

第 2 行到(n+1)行,每行一个整数。第 i 行的整数 ai 代表第 i 座山的海拔高度。

【输出】

输出一行一个整数,代表约翰需要支付修改山海拔高度的总金额。

【输入样例】

5
20
4
1
24
21

【输出样例】

18

【核心思想】

  1. 问题分析:给定 \(n\) 座山峰的海拔高度 \(a_i \in [0, 100]\),要求将所有山峰高度调整到某个区间 \([L, L+17]\) 内(\(L\) 为整数),使得调整总成本 \(\sum x_i^2\) 最小,其中 \(x_i\) 为每座山调整的高度。这是一个枚举 + 贪心问题,关键在于确定最优区间下界 \(L\)

  2. 算法选择

    • 排序预处理:将山峰高度从小到大排序,便于按区间处理
    • 枚举区间下界:由于原始高度范围 \([0, 100]\),最优区间 \([L, L+17]\)\(L\) 只需枚举 \([0, 83]\)(因 \(L+17 \leq 100\)
    • 贪心调整:对每个 \(L\),低于 \(L\) 的山峰提升到 \(L\),高于 \(L+17\) 的山峰降低到 \(L+17\),区间内的山峰不动
  3. 关键步骤

    • 读入数据\(n\) 和数组 \(a[1..n]\)
    • 排序:将 \(a\) 按升序排列
    • 枚举下界 \(L\)\(L\)\(0\)\(83\)):
      • 初始化 sum = 0
      • 遍历每座山 \(a_j\)
        • \(a_j < L\)sum += (L - a_j)^2(提升到 \(L\)
        • \(a_j > L + 17\):`sum += (a_j - L - 17)^2$(降低到 \(L+17\)
        • 若在 \([L, L+17]\) 内:不调整,成本为 0
      • 更新答案:minn = min(minn, sum)
    • 输出结果:最小总成本 \(minn\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(84 \cdot n) = O(n)\),枚举 84 个下界,每个遍历 \(n\) 座山
    • 空间复杂度:\(O(n)\),存储山峰高度数组
  5. 枚举区间的核心思想

    • 区间长度固定:题目要求极差 \(\leq 17\),即区间长度固定为 17,只需确定下界 \(L\)
    • 最优调整策略:对于固定区间 \([L, L+17]\),每座山独立决策——低于下限就提到下限,高于上限就降到上限,区间内不动。这是因为在区间约束下,每座山的最优调整就是投影到区间最近端点
    • 枚举范围压缩:原始高度 \(\in [0, 100]\)\(L\) 的有效范围仅为 \([0, 83]\),枚举量极小
    • 凸成本特性:成本函数 \(x^2\) 是凸函数,投影到区间的策略在独立约束下是最优的
    • 适用于带区间约束的最小化调整、固定窗口滑动、离散枚举优化等问题

【解题思路】

【算法标签】

普及- #贪心

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, a[1005], minn=1e9;
int main()
{
    cin >> n;  // 输入n
    for (int i=1; i<=n; i++) {  // 输入所有山峰高度
        cin >> a[i];
    }
    sort(a+1, a+n+1);  // 按照从大到小排序
    for (int i=0; i<=83; i++) {  // 遍历山峰的最小高度(最高高度就是i+17)
        int sum = 0;  // 定义每轮总金额,初始为0
        for (int j=1; j<=n; j++) {  // 遍历所有山峰
            if (a[j]<i) {  // 小于最小高度,就增加两者之差需要的成本
                sum += (i-a[j])*(i-a[j]);
            } else if (a[j]>i+17) {  // 高于最大高度,也增加两者之差需要的成本
                sum += (a[j]-i-17)*(a[j]-i-17);
            }
        }
        minn = min(minn, sum);  // 每轮统计后计算最小值
    }
    cout << minn << endl;  // 输出最小值
    return 0;
}

【运行结果】

5
20
4
1
24
21
18
posted @ 2026-08-03 08:48  团爸讲算法  阅读(8)  评论(0)    收藏  举报