1.Huffman树
Acwing 148.合并果子
实现思路:构建一颗哈夫曼树,求最短带权路径长度(树中所有的叶结点的权值乘上其到根结点的路径长度)
- 每次选择重量最小的两堆进行合并
- 使用小根堆存储每一堆果子,每次两次弹出堆顶元素,合并后就放入小根堆中
具体实现代码(详解版):
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n; // 输入元素数量
// 使用最小堆来保存元素
priority_queue<int, vector<int>, greater<int>> heap;
// 读取 n 个整数并将其压入最小堆
for (int i = 0; i < n; ++i) {
int x;
cin >> x;
heap.push(x);
}
int res = 0; // 保存合并的总代价
// 每次取出两个最小的元素,直到堆中只剩下一个元素
while (heap.size() > 1) {
int a = heap.top(); heap.pop(); // 取出堆中最小的元素
int b = heap.top(); heap.pop(); // 取出堆中第二小的元素
res += a + b; // 计算这次合并的代价,并累加到总代价
heap.push(a + b); // 将合并后的元素重新压入堆中
}
cout << res << endl; // 输出最终的总代价
return 0;
}
2. 排序不等式
Acwing 913.排队打水
实现思路:
假设各个同学的打水时间为:3 6 1 4 2 5 7
并且就按照这个顺序来打水。 当第一个同学打的时候,后面所有同学都要等他,所以等待的总时长要加上一个3 * 6
,第二个同学打的时候,后面所有同学也都要等他,所以要加上个6 * 5
,以此类推,所有同学等待的总时长为3 * 6 + 6 * 5 + 1 * 4 + 4 * 3 + 2 * 2 + 5 * 1
假设各个同学打水花费的时长为 t1,t2,t3,…,tn
,则按照次序打水,总的等待时长为:t1 * (n-1) + t2 * (n-2) + ... + tn * 1
。
可以看出,当打水顺序按照花费时间从小到大排序时,所得的等待时间最小
采用反证法(调整法),假设最优解不是按照从小到大的顺序,则必然存在2个相邻的人,前一个人打水时长比后一个大,即必然存在一个位置i,满足t_i > t_i+1,那我们尝试把这两个同学的位置交换,看看对总的等待时长有什么影响,这两个同学的交换,只会影响他们两的等待时长,不会影响其他同学的等待时长。 交换前,这部分等待时长为
t_i * (n-i) + t_i+1 * (n-i-1)
,交换后,这部分等待时长为t_i+1 * (n-i) + t_i * (n-i-1)
,容易算得,交换后的等待时长变小了,则该方案不是最优解,矛盾了。则最优解就是按照从小到大的顺序依次打水。
具体实现代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
typedef long long LL;
int n,t[N];
int main(){
cin >> n;
for(int i = 0 ; i < n ; i ++) cin >> t[i];
sort(t,t + n);
LL res = 0;
for(int i = 0 ; i < n ; i ++) res += t[i] * (n - i - 1);
cout << res << endl;
return 0;
}
3.绝对值不等式
Acwing 104.货舱选址
思路分析:
假设n
个商店在数轴上的坐标依次为:x1
,x2
,x3
,…,xn
设仓库的位置为x
,则总的距离为
f(x) = |x1 - x| + |x2 - x| + ... + |xn - x|
我们要求解的就是f(x)
的最小值。
我们可以先进行一下分组,1
和n
为一组,2
和n-1
为一组…
f(x) = (|x1 - x| + |xn - x|) + (|x2 - x| + |x_n-1 - x|) + ....
单独看一组,任意一组都可以写成形如|a - x| + |b - x|
的形式,a
和b
是已知的常数,x
是未知数。假设a < b
,则容易知道,当x
取值在[a,b]
这个区间内时,上面的表达式取得最小值b - a
,而x
取值只要落在[a,b]
区间外,则上面的表达式的值一定是大于b - a
的。
由此可知,对于分组1
和n
,只要x
取值在[x1,xn]
这个区间内,就能使|x1 - x| + |xn - x|
取得最小值xn - x1
。同理,对于|x2 - x| + |x_n-1 - x|
,只要x
取值在[x2,x_n-1]
区间内,就能使这个部分取得最小值x_n-1 - x2
…容易得出,只要取所有分组的区间的交集即整个区间的中间点,能使总的f(x)
最小。即,当n
为偶数时,x
只要落在最中间2个点之间即可;当n
为奇数时,x
只需要落在最中间的那个点上即可。
具体实现代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1000010;
int p[N];
int n;
int main(){
int n;
cin >> n;
for(int i = 0 ; i < n ; i ++) cin >> p[i];
sort(p,p + n);
int res = 0;
for(int i = 0 ; i < n ; i ++) res += abs(p[i] - p[n / 2]);
cout << res << endl;
return 0;
}
4.推公式
Acwing 125.耍杂技的牛
实现思路:wi表示牛的体重,si表示牛的强壮度
先给结论:按照w + s
从小到大的顺序,从上往下排,最大的危险系数一定是最小的。
简单理解:把重量轻的牛放下面是很亏的,同样把不强壮的牛放下面也是亏的,所以就尽可能把又重又强壮的牛放下面
就不证明了。
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int,int> PII;//存储牛的体重+强健度,体重
const int N = 50010;
PII cow[N];
int n;
int main(){
cin >> n;
for(int i = 0 ; i < n ; i ++){
int w,s;
cin >> w >> s;
cow[i] = {w + s,w};
}
sort(cow ,cow + n);//自动先按体重+强健度排序
int sum = 0,res = -2e9;
for(int i = 0 ; i < n ; i ++){
int w = cow[i].second,s = cow[i].first - w;//s为强健度
res = max(res,sum - s);
sum += w;
}
cout << res << endl;
}
总结:贪心算法(Greedy Algorithm)是一类在每一步选择中都采取当前最优策略,以期最终获得全局最优解的算法。贪心算法非常适用于解决局部最优能够导出全局最优的场景。在实际应用中,贪心算法通常比动态规划和回溯算法更为简单高效,但它并不总是能保证找到最优解。因此,贪心算法的应用需要证明其贪心策略是正确的,确保每一步的局部最优解能够累积成为全局最优解。
完结撒花~多复习