贪心问题
贪心问题很多都需要证明,但题目背后往往是那几个经典的类型。笔记用于积累,越做越有。
一、哈夫曼树
哈夫曼树的构造
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树。
哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。哈夫曼树是贪心算法的经典例子:总是从森林中选取两颗根节点权值最小的二叉树,作为一棵树新的左右子树,新二叉树的根节点的权值为左右子树根节点权值之和。从森林中删除原二叉树,并将新树加入到森林中。重复此过程直到森林中只有一颗二叉树。
算法实现
使用小根堆(优先队列)实现,这样总能选取到根节点权值最小的两颗二叉树。堆中存储的对象是权值。
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int main(){
int n;
scanf("%d",&n);
priority_queue<int,vector<int>,greater<int>> heap;
while(n--){
int x;
scanf("%d",&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);
}
printf("%d\n",res);
return 0;
}
二、排序不等式
排序不等式分析
排队打水问题是经典的排序不等式问题:
要保证打水的总等待时间最短,则打水时间较短的人先打水。
类比短进程优先调度算法( SJF )可使进程的平均等待时间最短。
算法实现
思路1:打水时间最短的被等待的次数最多。
思路2:打水时间最长的要等待之前所有打水完成之后才开始。
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=100010;
int n,a[N];
int main(){
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
sort(a,a+n);
LL res=0;
for(int i=n-1,j=0;i>=0;i--,j++)
res+=j*a[i];
cout<<res<<endl;
return 0;
}
绝对值不等式问题
绝对值不等式分析
货仓选址问题是经典的绝对值不等式问题:
对于两个点:
∣
x
−
a
∣
+
∣
x
−
b
∣
>
=
∣
a
−
b
∣
|x-a|+|x-b|>=|a-b|
∣x−a∣+∣x−b∣>=∣a−b∣等号成立的条件是 x 位于两个点之间(包括端点)。
对于 n 个点则分组考虑:货仓在x坐标处,x 左侧的商店有 P 家,x 右侧的商店有 Q 家。当P == Q时为最优解(总是位于左右两个点之间)。n 为奇数时,x 在n个点的中位数上为最佳位置;n 为偶数时,x 最中间两个点之间任意位置。
该问题还可以推广到 2 维、3 维(三分)、n 维(模拟退火)等。
算法实现
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int n,a[N];
int main(){
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
sort(a,a+n);
int res=0;
for(int i=0;i<n;i++)
res+=abs(a[i]-a[n/2]);
cout<<res<<endl;
return 0;
}
推公式类问题
此类问题需要结合具体题目分析,如国王游戏、耍杂技的牛等问题。
无论公式如何,贪心问题先排序。