CodeVs 1063
/*
首先理解小(大)根堆的概念
即将数据以树的结构处理
小根堆: 每个父节点都比其叶子小
大根堆: 每个父节点都比其叶子大
这样,保证了这组数据较为有序,且根节点毕竟为最值
然后取最值,并删除这个数【处理方法为覆盖为最后一个数】,再次维护一下根节点,不断重复,即实现了堆排序
然后以CodeVs 1063 「合并果子」这道经典题来看这种最小堆的实际用途
分析:
基础思路肯定为贪心,不断取最小的两个合并,得到的肯定为最小的
然而合并过后得到的值还要放回去,则仍需要排序维护以得到最小两个
因此建立小根堆,放回去后只需维护其所选择的那个节点便行了
*/
#include<bits/stdc++.h>
using namespace std;
int Many,Stack[10000];
void Swap(int, int, int);
void Mainten(int Top, int Tail) //建立小根堆
{
if (Top*2 > Tail) return; //没有儿子,直接退出
if (Top*2+1 > Tail) //只有左儿子
{
if (Stack[Top] > Stack[Top*2]) Swap(Top,Top*2,Tail);}
else
{ //左右儿子都有,取最小放上去
int MinTop=Stack[Top*2]<Stack[Top*2+1]?Top*2:Top*2+1;
if (Stack[Top] > Stack[MinTop]) Swap(Top,MinTop,Tail);
}
}
void Swap(int A, int B, int Tail)
{
int t=Stack[A];
Stack[A]=Stack[B],Stack[B]=t;
Mainten(B,Tail); //交换了后,一定要记得维护,注意这里是上至下维护,因此后面会有个易错点
}
int main()
{
cin >> Many;
int Tail=Many,Ans=0;
for (int i=1; i<=Many; i++) cin >> Stack[i];
if (Many == 1) cout << Stack[1];
sort(Stack+1,Stack+1+Many); //刚开始可以直接sort,跟堆排效果一样的……
for (int i=1; i<=Many-1; i++)
{
int Top=Stack[2]>Stack[3]&&Tail>2?3:2; //这里比较根节点的左右儿子选择最小值
//易错: 一定要判断是否堆里剩余个数大于2个,即根节点左右儿子均有,不然肯定出错,【选择了不存在的右儿子
Ans+=Stack[1]+Stack[Top]; //然后合并最小的两个,并累加到结果ans里
Stack[1]=Stack[1]+Stack[Top]; //将所合并的覆盖回去
Stack[Top]=Stack[Tail]; //将最后一个覆盖到所选的左右儿子
//因为是选择了其中两个,然后要放回一个,便是以上操作
Tail--; //结合这步,成功删除了两个用过的最小值,并将计算结果放回到堆中
Mainten(Top,Tail); //这两步维护最小堆
Mainten(1,Tail); //易错!: 一定要倒着,先维护Top在维护根,因为维护操作中我是更改顺序后只调整儿子节点,即可能第一个小于第二个第三个,但第二个不一定小于第五个第六个,但换了之后不会维护其父亲
}
if (Ans != 0) cout << Ans; //如果Many == 1的话ans会是零,这里加个特判点
}