前言
感觉自己最近做的题难度上去了点,但是经过学习,又觉得没那么难了。
还是先放题目来源:P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G
题目理解:
题目抽象过来的意思,如果理解了,会发现它跟霍夫曼编码的过程很像。即,每次给出n个数,需要我们先选出两个最小的数相加,然后将加和放到原序列里并在原序列中去除相加的两个数,同时二者的加和要被记录到一个总和sum里。对新序列重复执行寻找最小的数,相加,把加和加到总和sum里,更新序列,直到原序列只剩一个数为止。
解题思路:
(1)最朴素的做法:利用选择排序,每次选最小的两个数,然后求加和,在原序列中去掉最小的两个数并把新的加和加入到序列里,并算总和。这样做的话需要一个记录数是否存在的数组来维护“存在/去掉”的状态。同时可以把新加和放到被删去的任意一个数的位置来减小空间开销。但是由于选出两个数来时间复杂度为O(
n
n
n),而选出所有情况得到最后的一个数又要
n
n
n次,因此最终时间复杂度为O(
n
2
n^2
n2),很容易造成TLE。
(2)相对可行的另一个办法:既然每次都只需要知道最小的两个数,自然而然可以让人想到堆这个结构,并且符合本题题意的是小顶堆。回忆堆排序,在顺序存储结构上做堆排序,本质需要利用一个Heapadjust()函数,来对原数组进行从left到right区间上的建堆过程,而堆排序又是先从完全二叉树的最后一个非终端节点起依次进行Heapadjust()操作,直到进行到序号为1的位置;然后将堆顶和末尾进行对调,并进行下一轮堆排序。这样的话,很容易联想到本题要借助堆排序来做。这就是说,我们可以先对数组a[n+1](从a[1]开始记录)进行一次堆调整使得其成为一个小顶堆,然后交互堆顶和末尾,然后对末尾前一个位置到堆顶进行堆调整使得堆顶和堆尾分别为两个最小的数,然后利用for循环去模拟原题中果子合并的过程,这样就可以很轻松求得正确答案。
#include <stdio.h>
#include <stdlib.h>
void Heapadjust(long long int a[],long long int left,long long int right){
long long int rc=a[left],i;
for(i=left*2;i<=right;i*=2){
if(i<right&&a[i+1]<a[i])
i++;
if(rc<=a[i])
break;
else{
a[left]=a[i];
left=i;
}
}
a[left]=rc;
return;
}
int main(int argc, char *argv[]) {
long long int n,i,sum=0,switch_num;
scanf("%lld",&n);
long long int a[n+1];
for(i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(i=n/2;i>=1;i--)
Heapadjust(a,i,n);
for(i=n;i>=1;i--){
if(i==n){
switch_num=a[i];
a[i]=a[1];
a[1]=switch_num;
Heapadjust(a,1,i-1);
}else if(i==1){
sum+=a[1]+a[2];
}
else{
sum+=a[i+1]+a[1];
a[1]+=a[i+1];
Heapadjust(a,1,i);
switch_num=a[1];
a[1]=a[i];
a[i]=switch_num;
Heapadjust(a,1,i-1);
}
}
printf("%lld",sum);
return 0;
}