最近在找房子

再有十来天就要搬家了,可是房子还没有找到。:(

再次出来找房子,才发现周边的房租也和如今的房价一样,窜升了不少。也许是在原来的房子住惯了, 想找到和原来一样家电齐全的房子。但是这需要比以前多付出好几百,对我来说,这已经快超出了我的承受范围。

于是,就在希望、看房、失望中度过了几天。

看到了阴暗地下室中一间间不透风的小房子,看到了旧居民区中满地乱爬的“小强”,破碎的窗户,看到了四五个女孩子合租一套小房子,为了便宜一点钱苦苦的和房东交涉,看到了四面都是灰,墙面脱落,只有两张小床,位置不好,也居然和我现在的房子要一样的价……

大城市光鲜的表面里面,原来还有那么多令人心酸的地方。

原来,还有那么多比我要苦得多的人,还有什么好说的呢。

什么时候房价才能稳定下来,让我们不在为房子而奔波,为了有一个容身之地而耗尽毕生的心血呢?

安得广厦千万间,大辟天下寒士俱欢颜!

# 题目重述 已知 $ n $ 栋楼房的原始高度,要求调整这些楼房的高度,使得任意两栋楼之间的高度差不超过 $ m $ 米。调整一栋楼高度变化 $ k $ 米的花费为 $ k^2 $ 金币。求满足条件的最小总花费。 **输入格式:** 第一行两个整数 $ n, m $($ 1 \leq n, m \leq 2 \times 10^5 $)。 第二行 $ n $ 个整数 $ h_i $ 表示每栋楼的原始高度($ 1 \leq h_i \leq 2 \times 10^5 $)。 **输出格式:** 输出一个整数,表示最小总花费。 --- # 详解 我们要选择一组新的高度 $ h'_i $,使得: - $ \max(h') - \min(h') \leq m $ - 总花费 $ \sum (h_i - h'_i)^2 $ 最小 **关键观察:** 最优解中,最终所有楼的高度一定落在某个区间 $[L, L + m]$ 内。我们可以枚举这个区间的左端点 $ L $,然后对每个原始高度 $ h_i $ 计算在该区间内调整后的最佳目标值,使得 $ (h_i - x)^2 $ 最小。 对于每个 $ h_i $: - 如果 $ h_i < L $,则必须调高到 $ L $,代价为 $ (h_i - L)^2 $ - 如果 $ h_i > L + m $,则必须调低到 $ L + m $,代价为 $ (h_i - (L + m))^2 $ - 否则可保持不变或微调至最近点,即取 $ h_i $ 在 $[L, L+m]$ 内的投影,代价为 $ (h_i - \text{clamp}(h_i, L, L+m))^2 $ 于是我们可以通过以下步骤求解: 1. 将原始高度排序。 2. 枚举所有可能的区间 $[L, L + m]$,其中 $ L $ 可以从 $ \min(h_i) - \text{offset} $ 到 $ \max(h_i) $ 范围内选取,但考虑到数据范围,我们只枚举所有“有意义”的 $ L $ 值(例如以现有高度为基础滑动窗口)。 3. 对每个区间快速计算总代价。 但由于直接枚举 $ L $ 效率低,我们需要优化。 **更优做法:双指针 + 中位数贪心?** 然而注意到:当固定最终区间 $[L, R]$, $ R = L + m $,每个点的最优映射是 clamp 操作,代价函数是分段二次的。 但这类问题通常采用 **枚举最终区间的下界和上界** 并结合前缀和优化的方法。 实际上这是一个经典问题,可用如下方法解决: ✅ **正确解法思路(离散化 + 枚举目标区间):** 由于所有高度和 $ m $ 不超过 $ 2 \times 10^5 $,我们可以考虑枚举最终的目标区间 $[x, x + m] $,然后计算将所有 $ h_i $ 投影到该区间内的最小花费 $ \sum (\text{clamp}(h_i, x, x + m) - h_i)^2 $。 但是枚举所有 $ x \in [0, 2 \times 10^5] $ 是可行的(最多 $ 2 \times 10^5 $ 次),每次计算总花费 $ O(n) $,总体 $ O(n \cdot \max_h) \approx 4 \times 10^{10} $ —— 超时! 需要优化。 ✅ **优化方法:事件扫描法(Event Method)** 我们将每个 $ h_i $ 视为一个点,其对不同区间 $[x, x + m]$ 的贡献分为三种情况: - 当 $ x \leq h_i \leq x + m $:代价为 0 - 当 $ h_i < x $:代价为 $ (x - h_i)^2 $ - 当 $ h_i > x + m $:代价为 $ (h_i - x - m)^2 $ 令 $ f(x) = \sum_{h_i < x} (x - h_i)^2 + \sum_{h_i > x + m} (h_i - x - m)^2 $ 目标是最小化 $ f(x) $,其中 $ x \in [\min h_i - m, \max h_i] $,但实际上只需要在 $ x \in [0, \max h_i] $ 枚举。 我们按 $ x $ 扫描,维护: - 所有 $ h_i < x $ 的个数与平方展开项(用于快速算 $ \sum (x - h_i)^2 = cnt \cdot x^2 - 2x \sum h_i + \sum h_i^2 $) - 所有 $ h_i > x + m $ 的类似信息 可以预处理排序后使用双指针移动边界,并用前缀和维护两部分的数据。 **算法步骤:** 1. 排序 $ h $ 2. 预处理前缀和数组(个数、和、平方和) 3. 枚举 $ x $(实际只需枚举某些候选点,如所有 $ h_i $ 和 $ h_i - m $) 4. 使用二分到 $ h_i < x $ 的集合,以及 $ h_i > x + m $ 的集合 5. 分别计算这两部分的代价 6. 更新最小值 但考虑到 $ n \leq 2 \times 10^5 $,若对每个候选 $ x $ 都进行二分+前缀和查询,复杂度约为 $ O(\text{range} \cdot \log n) $,仍然较高。 ✅ **实际可行策略:枚举所有可能的 $ L \in [minH - m, maxH] $,但只取关键点** 注意到:最优的 $ L $ 必然出现在某个 $ h_i $ 或 $ h_i - m $ 附近(因为代价函数是分段二次函数,极值点在断点处) 所以我们收集所有候选 $ L $: - $ L = h_i - m $ - $ L = h_i $ 并对每个候选区间 $[L, L + m] $ 计算总代价,取最小。 去重后最多 $ 2n $ 个候选值,每个计算 $ O(n) $,总复杂度 $ O(n^2) $,最坏 $ 4 \times 10^{10} $,仍不可行。 ⚠️ 此题正解应基于 **凸性 + 三分搜索** 或 **斜率优化**,但更常见的是使用 **滑动窗口 + 前缀和优化枚举**。 ✅ **推荐解法:枚举最终区间的下界 $ L $,利用排序+双指针+前缀和加速计算** **核心思想:** - 设最终所有楼高度在 $[L, L + m]$ - 所有 $ h_i < L $ 的要调到 $ L $,代价 $ (L - h_i)^2 $ - 所有 $ h_i > L + m $ 的要调到 $ L + m $,代价 $ (h_i - L - m)^2 $ - 中间的不用变 我们对 $ h $ 排序后,随着 $ L $ 增大,$ h_i < L $ 的数量递增,$ h_i > L + m $ 的数量递减。可以用双指针维护这两个集合。 具体实现: 1. 排序 $ h $ 2. 枚举所有可能的 $ L $ 值(取所有 $ h_i $ 和 $ h_i - m $ 作为候选) 3. 去重并排序候选 $ L $ 4. 对每个候选 $ L $: - 用 lower_bound 到 $ h_i < L $ 的部分 → 调整到 $ L $ - 用 upper_bound 到 $ h_i > L + m $ 的部分 → 调整到 $ L + m $ - 中间部分不调整 - 使用预处理的前缀和快速计算两部分的平方代价 **平方和公式:** $$ \sum (a - b_i)^2 = cnt \cdot a^2 - 2a \sum b_i + \sum b_i^2 $$ 因此可预处理前缀和(个数、和、平方和) --- # 代码实现 ```cpp #include <iostream> #include <vector> #include <algorithm> #include <climits> using namespace std; typedef long long ll; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; vector<ll> h(n); for (int i = 0; i < n; i++) { cin >> h[i]; } sort(h.begin(), h.end()); // 预处理前缀和:cnt, sum, sq_sum vector<ll> pre_cnt(n + 1, 0), pre_sum(n + 1, 0), pre_sq(n + 1, 0); for (int i = 0; i < n; i++) { pre_cnt[i + 1] = pre_cnt[i] + 1; pre_sum[i + 1] = pre_sum[i] + h[i]; pre_sq[i + 1] = pre_sq[i] + h[i] * h[i]; } // 获取区间 [l_idx, r_idx) 的前缀和信息 auto getSum = [&](int l, int r, vector<ll>& arr) -> ll { if (l >= r) return 0; return arr[r] - arr[l]; }; // 收集候选 L 值 vector<ll> candidates; for (ll height : h) { candidates.push_back(height); candidates.push_back(height - m); } sort(candidates.begin(), candidates.end()); candidates.erase(unique(candidates.begin(), candidates.end()), candidates.end()); ll min_cost = LLONG_MAX; for (ll L : candidates) { if (L < 0) continue; // 高度不能为负(可选限制) ll R = L + m; // h[i] < L 的部分 -> 调整到 L int left_end = lower_bound(h.begin(), h.end(), L) - h.begin(); ll cnt1 = left_end; ll sum1 = getSum(0, left_end, pre_sum); ll sq1 = getSum(0, left_end, pre_sq); ll cost1 = cnt1 * L * L - 2 * L * sum1 + sq1; // h[i] > R 的部分 -> 调整到 R int right_start = upper_bound(h.begin(), h.end(), R) - h.begin(); ll cnt2 = n - right_start; ll sum2 = getSum(right_start, n, pre_sum); ll sq2 = getSum(right_start, n, pre_sq); ll cost2 = cnt2 * R * R - 2 * R * sum2 + sq2; ll total_cost = cost1 + cost2; if (total_cost < min_cost) { min_cost = total_cost; } } cout << min_cost << endl; return 0; } ``` --- # 知识点 - **枚举+剪枝优化**:通过分析最优解结构,仅枚举关键候选值降低复杂度。 - **前缀和加速计算**:预处理个数、和、平方和,快速计算批量二次代价。 - **二分查定位区间**:利用有序数组快速到需调整的元素范围。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值