贪心算法,优先队列(大小根堆使用)

P1090 [NOIP 2004 提高组] 合并果子

题目背景

P6033 为本题加强版。

题目描述

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n − 1 n-1 n−1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 1 1 1 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

例如有 3 3 3 种果子,数目依次为 1 1 1 , 2 2 2 , 9 9 9 。可以先将 1 1 1 、 2 2 2 堆合并,新堆数目为 3 3 3 ,耗费体力为 3 3 3 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12 12 12 ,耗费体力为 12 12 12 。所以多多总共耗费体力 = 3 + 12 = 15 =3+12=15 =3+12=15 。可以证明 15 15 15 为最小的体力耗费值。

输入格式

共两行。

第一行是一个整数 n ( 1 ≤ n ≤ 10000 ) n(1\leq n\leq 10000) n(1≤n≤10000) ,表示果子的种类数。

第二行包含 n n n 个整数,用空格分隔,第 i i i 个整数 a i ( 1 ≤ a i ≤ 20000 ) a_i(1\leq a_i\leq 20000) ai(1≤ai≤20000) 是第 i i i 种果子的数目。

输出格式

一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 2 31 2^{31} 231 。

输入输出样例 #1

输入 #1

复制代码
3 
1 2 9

输出 #1

复制代码
15

说明/提示

对于 30 % 30\% 30% 的数据,保证有 n ≤ 1000 n \le 1000 n≤1000;

对于 50 % 50\% 50% 的数据,保证有 n ≤ 5000 n \le 5000 n≤5000;

对于全部的数据,保证有 n ≤ 10000 n \le 10000 n≤10000。

deepseek总结

贪心算法

核心思想

贪心算法 在每一步选择中都采取在当前状态下最好最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。

简单来说,就是 "每一步都只看眼前利益,做出当前最好的选择,不考虑长远"


贪心算法的特征

一个问题能否用贪心算法解决,通常取决于它是否具备以下两个性质:

  1. 贪心选择性质

    • 一个问题的全局最优解可以通过一系列局部最优的选择(贪心选择)来达到。
    • 关键:我们可以通过做出当前最好的选择来构造一个全局最优解,而不必考虑子问题的解。
  2. 最优子结构

    • 一个问题的最优解包含其子问题的最优解。
    • 这和动态规划是一样的,但贪心在做出选择后,不会回退

与动态规划(DP)的对比

这是理解贪心的关键。我们通过一个表格来对比:

方面 贪心算法 (Greedy) 动态规划 (DP)
决策依据 每一步只根据当前状态做出最优选择 每一步的选择依赖于子问题的解
历史记录 不记录历史,选择后不可回退 记录所有可能的历史状态(通常用数组)
回退 无回退,一条路走到黑 会考虑各种可能性,本质是枚举+剪枝
效率 通常高效,时间复杂度低 通常相对较低,因为要处理更多状态
适用问题 问题具有贪心选择性质 问题具有重叠子问题最优子结构
思路 自顶向下 通常自底向上

通俗比喻

  • 贪心 :像是一个只考虑眼前利益的登山者,每次都选择最陡的路向上爬,希望尽快登顶,但可能最终只到达了一个小山头(局部最优)。
  • 动态规划 :像是一个谨慎的规划师,他会预先画好地图,考虑所有可能的路线,并记录下每条路线的信息,最终选择一条绝对最佳的路线(全局最优)。

常见应用场景

  1. 哈夫曼编码:用于数据压缩。
  2. 最小生成树:Prim算法和Kruskal算法。
  3. 单源最短路径:Dijkstra算法(权值为非负时)。
  4. 区间调度问题:如活动安排。
  5. 部分背包问题:物品可以分割时。

总结

  • 核心:每一步都选择当前最优解。
  • 关键 :问题必须具有贪心选择性质最优子结构
  • 难点 :如何选择和证明贪心策略的正确性。
  • 座右铭:"活在当下",但前提是"当下"的选择能通向往"美好的未来"。

小根堆

一、核心概念总览

特性 小根堆 (Min Heap) 大根堆 (Max Heap)
核心性质 父节点的值 所有子节点的值 父节点的值 所有子节点的值
堆顶元素 全局最小值 全局最大值
主要操作 插入、获取最小值、删除最小值 插入、获取最大值、删除最大值
时间复杂度 插入/删除: O(log n), 获取最值: O(1) 插入/删除: O(log n), 获取最值: O(1)
结构 完全二叉树 (通常用数组实现) 完全二叉树 (通常用数组实现)
直观理解 一个能随时拿到最小元素的"盒子" 一个能随时拿到最大元素的"盒子"

二、C++ STL 中的 priority_queue

priority_queue 是 STL 对堆数据结构的封装实现,让你无需手动维护堆。

1. 默认行为:大根堆
cpp 复制代码
#include <queue>
priority_queue<int> pq; // 默认就是大根堆

pq.push(3);
pq.push(1);
pq.push(4);
cout << pq.top(); // 输出 4 (当前最大值)
2. 如何定义小根堆

需要通过模板参数指定比较方式:

cpp 复制代码
#include <queue>
#include <vector>
#include <functional> // 需要包含这个头文件

// 小根堆的定义方式
priority_queue<int, vector<int>, greater<int>> min_pq;

min_pq.push(3);
min_pq.push(1);
min_pq.push(4);
cout << min_pq.top(); // 输出 1 (当前最小值)
3. 模板参数解释
cpp 复制代码
priority_queue<T, Container, Compare>
  • T : 元素类型 (如 int, string)
  • Container : 底层容器,默认为 vector<T>
  • Compare : 比较规则,默认为 less<T> (大根堆),小根堆用 greater<T>
4. 常用操作
操作 函数 时间复杂度 说明
插入元素 push(val) O(log n) 插入并调整堆
访问堆顶 top() O(1) 返回优先级最高的元素
删除堆顶 pop() O(log n) 删除堆顶并调整堆
判断空 empty() O(1) 队列是否为空
元素数量 size() O(1) 返回元素个数

三、关键要点总结

1. 核心区别
  • 大根堆:大的优先级高,堆顶是最大值
  • 小根堆:小的优先级高,堆顶是最小值
2. STL 使用技巧
cpp 复制代码
// 大根堆 (默认)
priority_queue<int> max_heap;

// 小根堆 (必须记住的语法)
priority_queue<int, vector<int>, greater<int>> min_heap;

// 自定义比较器
struct Compare {
    bool operator()(const Person& a, const Person& b) {
        return a.age < b.age; // 大根堆:年龄大的优先级高
    }
};
priority_queue<Person, vector<Person>, Compare> custom_pq;
3. 应用场景
  • 大根堆:找最大值、Top K 大问题、任务调度(优先级高的先执行)
  • 小根堆:找最小值、Top K 小问题、Dijkstra算法、哈夫曼编码
4. 竞赛建议
  • 大部分情况 :直接用 priority_queue,节省编码时间
  • 特殊需求:理解底层原理,必要时手动实现
  • 性能关键:了解复杂度,避免错误使用

四、一句话总结

  • 小根堆:最小的在最上面
  • 大根堆:最大的在最上面
  • priority_queue :C++ STL 帮你实现好的堆,默认是大根堆,通过 greater<T> 可以变成小根堆

leetcode:2611

有两只老鼠和 n 块不同类型的奶酪,每块奶酪都只能被其中一只老鼠吃掉。

下标为 i 处的奶酪被吃掉的得分为:

如果第一只老鼠吃掉,则得分为 reward1[i] 。

如果第二只老鼠吃掉,则得分为 reward2[i] 。

给你一个正整数数组 reward1 ,一个正整数数组 reward2 ,和一个非负整数 k 。

请你返回第一只老鼠恰好吃掉 k 块奶酪的情况下,最大 得分为多少。

示例 1:

输入:reward1 = [1,1,3,4], reward2 = [4,4,1,1], k = 2

输出:15

解释:这个例子中,第一只老鼠吃掉第 2 和 3 块奶酪(下标从 0 开始),第二只老鼠吃掉第 0 和 1 块奶酪。

总得分为 4 + 4 + 3 + 4 = 15 。

15 是最高得分。

示例 2:

输入:reward1 = [1,1], reward2 = [1,1], k = 2

输出:2

解释:这个例子中,第一只老鼠吃掉第 0 和 1 块奶酪(下标从 0 开始),第二只老鼠不吃任何奶酪。

总得分为 1 + 1 = 2 。

2 是最高得分。

注意贪心的对象,贪心的是差值

cpp 复制代码
class Solution {
public:
    int miceAndCheese(vector<int>& reward1, vector<int>& reward2, int k) {
        int n = reward1.size();
        priority_queue<int> que;
        long long ans = 0;
        for(int i = 0; i < n; i++){
		    que.push(reward1[i]-reward2[i]);
		    ans += reward2[i];
	    }
        for(int i = 0; i < k; i++){
		    ans += que.top();
		    que.pop();
	    } 
        return ans;
    }
};
相关推荐
小欣加油2 小时前
leetcode 474 一和零
c++·算法·leetcode·职场和发展·动态规划
一只老丸3 小时前
HOT100题打卡第36天——二分查找
数据结构·算法
陌路203 小时前
S19 哈希--6种哈希构造方法
算法·哈希算法
散峰而望3 小时前
C++入门(算法) - 习题
开发语言·c++·算法·github
这张生成的图像能检测吗3 小时前
(论文速读)Regor - 渐进式对应点再生实现鲁棒3D配准
人工智能·算法·计算机视觉·配准·3d点云
leoufung4 小时前
贪心算法理论与应用——以股票买卖问题为例
算法·贪心算法
墨雪不会编程5 小时前
数据结构—排序算法篇三
数据结构·算法·排序算法
CoovallyAIHub5 小时前
外科医生离手术世界模型还有多远?首次提出SurgVeo基准,揭示AI生成手术视频的惊人差距
深度学习·算法·计算机视觉