Time Limit: 1 second
Memory Limit: 128 MB
【问题描述】
瑞瑞想要亲自修复在他的一个小牧场周围的围栏。他测量栅栏并发现他需要N(1≤N≤20,000)根木板,每根的长度为整数Li(1≤Li≤50,000)。于是,他神奇地买了一根足够长的木板,长度为所需的N根木板的长度的总和,他决定将这根木板切成所需的N根木板。(瑞瑞在切割木板时不会产生木屑,不需考虑切割时损耗的长度) 瑞瑞切割木板时使用的是一种特殊的方式,这种方式在将一根长度为x的模板切为两根时,需要消耗x个单位的能量。瑞瑞拥有无尽的能量,但现在提倡节约能量,所以作为榜样,他决定尽可能节约能量。显然,总共需要切割N-1次,问题是,每次应该怎么切呢?请编程计算最少需要消耗的能量总和。【输入格式】
第一行: 整数N,表示所需木板的数量 第2到N+1行: 每行为一个整数,表示一块木板的长度
【输出格式】
一个整数,表示最少需要消耗的能量总和
【数据规模】
Sample Input1
3 8 5 8
Sample Output1
34
【样例说明】
将长度为21的木板,第一次切割为长度为8和长度为13的,消耗21个单位的能量,第二次将长度为13的木板切割为长度为5和8的,消耗13个单位的能量,共消耗34个单位的能量,是消耗能量最小的方案。【题解】
这题需要反过来想。
把一块长木板分成n个小木板,它的逆过程。就是
把n块小木板拼成1块长木板。
然后本来是每次消耗所需要切割的长度的体力。
现在变成消耗掉两块木板拼起来的长度等价的体力。
然后依然是操作n-1次。
则。每次只要选择最短和次短的两块木板拼起来就可以了。这样可以保证每次进行的操作都是花费最小的。
而维护最小值。需要用到堆(小根堆)。
【代码】
#include <cstdio> __int64 dui[20001] = { 0 };//记录堆的信息。小根堆。 int n,pos; void up_adjust(int p)//从位置p开始往上调整堆。 { __int64 x = dui[p];//先记录堆中这个元素的大小。 int i = p, j = p / 2; while (j > 0)//如果还没到根节点。 { if (x < dui[j])//如果需要调整则调整 { dui[i] = dui[j]; i = j; j = i / 2; } else//不需要调整了就结束。 break; } dui[i] = x; pos = i;//记录下新的位置。方便继续尝试往下调整。 } void down_adjust(int p)//从位置p开始往下调整 { __int64 x = dui[p]; int i = p, j = p * 2; while (j <= dui[0])//如果没到叶子节点 { if (j < dui[0] && dui[j + 1] < dui[j])//如果右儿子更小则和右儿子尝试交换。 j++; if (x > dui[j])//如果需要调整,则调整。 { dui[i] = dui[j]; i = j; j = i * 2; } else break; } dui[i] = x;//把这个元素放到新的位置。 } void input_data() { scanf("%d", &n);//输入数据 for (int i = 1; i <= n; i++)//依次把n个数据加入到堆中去。 { int x; scanf("%d", &x); dui[0]++; dui[dui[0]] = x; up_adjust(dui[0]);//因为加到叶子节点。所以要先往上调整。 down_adjust(pos);//然后再往下调整。 } } void get_ans() { __int64 ans = 0;//答案一开始为0 for (int i = 1; i <= n - 1; i++) { __int64 x = dui[1];//取出一个元素。进行调整 dui[1] = dui[dui[0]]; dui[0] --; down_adjust(1); __int64 y = dui[1];//再取出另一个次小的。再调整。 dui[1] = dui[dui[0]]; dui[0]--; down_adjust(1); __int64 z = x + y;//把这两个木块拼在一起。 ans += z;//累加答案。 dui[0]++;//把拼出来的新木板再加入到堆中去。 dui[dui[0]] = z; up_adjust(dui[0]);//向上调整。再向下调整即可。 down_adjust(pos); } printf("%I64d ", ans); } int main() { input_data(); get_ans(); return 0; }