Name: P1097合并果子
Copyright: 始发于goal00001111的专栏;允许自由转载,但必须注明作者和出处
Author: goal00001111
Date: 11-12-08 15:15
Description:
描述 Description
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。每一次合并,多多可以把两堆果子合并到一起,
消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。
所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。
输入格式 Input Format
输入包括两行,第一行是一个整数n(1<=n<=10000),表示果子的种类数。
第二行包含n个整数,用空格分隔,第i个整数ai(1<=ai<=20000)是第i种果子的数目。
输出格式 Output Format
输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于2^31。
样例输入 Sample Input
3 1 2 9
样例输出 Sample Output
15
题目分析:
本题的指导思想并不难,就是将所有的果子堆数按增序排列,每次将最小的两个堆合并,直到堆数为1为止,这样消耗的体力肯定是最少的。
这和构造赫夫曼树的算法不是一般的相似啊!
我们可以构造一棵赫夫曼树,累计所有非叶子结点的值就得到了最小体力总数。
没有人想超时,所以我们在构造赫夫曼树的时候,寻找两个最小结点的算法是不能用时间复杂度为O(N)的常规方法的,我这里使用了一个二叉堆,这样在寻找最小结点时时间复杂度为O(logN)。
除此之外我们还有一种方法:队列算法。
就是构造两个队列oldQueue 和 newQueue,oldQueue用来存储原有的果子堆数,
newQueue用来存储每次合并得到的新果子堆数,当两个队列都遍历完后,累加newQueue的元素值,就得到了总共耗费的体力值。
要想得到最小的体力耗费值则 oldQueue要按增序排列,很明显每次合并时候是在oldQueue 和 newQueue的首部 取两个最小值,合并的值插入到 newQueue尾部。
此种方法的好处是只需对oldQueue进行一次排序,之后就不再需要排序,而是直接在oldQueue 和 newQueue的首部取值就好了。
由于oldQueue的元素是从a[]中复制过来的,所以我们可以对其进行插入排序,这样排序的时间复杂度是O(N*logN);但如果我们注意观察的话,可以发现输入数据1<=ai<=20000,我们完全可以使用基数排序,以空间换时间,使得时间复杂度降低到O(N)。
说明:
算法思想:迭代和递归。
数据结构:数组,队列,结构数组(赫夫曼树),二叉堆。
时间复杂度:赫夫曼编码法O(N/2*logN + logN!),队列法:插入排序O(N*logN),基数排序O(N);
空间复杂度:O(MAX);
程序语言:分别用c++和pascal实现。
附注:分别提供了赫夫曼编码法和队列法实现代码。
关于赫夫曼编码的详细内容请参考拙作《赫夫曼编码》:
http://blog.csdn.net/goal00001111/archive/2008/12/16/3533984.aspx
方法一:赫夫曼编码:
c++代码:
#include
using namespace std;
typedef struct hNode
{
int weight;
int lc, rc;
} *HuffmanTree;
HuffmanTree CreateHuffmanTree(const int w[], int n);//创建一棵赫夫曼树
void BuildHeap(HuffmanTree t, int n); //构造一个二叉堆;小顶堆
void PercDown(HuffmanTree t, int pos, int n);//构造二叉堆的功能子函数
void DeleteMin(HuffmanTree t, int len); //删除二叉堆的根,并通过上移使得新得到的序列仍为二叉堆
void InsertHfNode(HuffmanTree t, int len, struct hNode x); //把x插入到原长度为len-1的二叉堆
void Preorder(HuffmanTree t, int p, unsigned long long & sum);//先序遍历赫夫曼树,累计非叶子结点的值
int main(int argc, char* argv[])
{
int n;
cin >> n;
int *a = new int[n];
for (int i=0; i cin >> a[i];
HuffmanTree hT = CreateHuffmanTree(a, n);
unsigned long long sum = 0;
Preorder(hT, 1, sum);//先序遍历赫夫曼树,累计非叶子结点的值
if (n == 1) //只有一个元素的情况
sum = a[0];
cout << sum << endl;
system("pause");
return 0;
}
//创建一棵赫夫曼树
HuffmanTree CreateHuffmanTree(const int w[], int n)
{
HuffmanTree hT = new struct hNode[2*n]; //第一个结点不用
for (int i=0; i {
hT[i+1].weight = w[i];
hT[i+1].lc = hT[i+1].rc = 0;
}
BuildHeap(hT, n);//构造一个二叉堆;小顶堆
struct hNode add;
int left = n;
int right = n;
while (left > 1) //此处通过减小和增大left的值来改变二叉堆的大小
{
hT[++right] = hT[1];
add.weight = hT[1].weight;
add.lc = right; //存储左孩子下标
DeleteMin(hT, left--);
hT[left+1] = hT[1];
add.weight += hT[1].weight;
add.rc = left+1; //存储右孩子下标
DeleteMin(hT, left);
InsertHfNode(hT, left, add);
}
return hT;
}
//构造一个二叉堆;小顶堆
void BuildHeap(HuffmanTree t, int len)
{
for (int i=len/2; i>0; i--)
{
PercDown(t, i, len);
}
}
//构造二叉堆的功能子函数
void PercDown(HuffmanTree t, int pos, int len)
{
int child;
struct hNode min = t[pos];
while (pos * 2 <= len)
{
child = pos * 2;
if (child != len && t[child+1].weight < t[child].weight)
child++;
if (min.weight > t[child].weight)
t[pos] = t[child];
else
break;
pos = child;
}
t[pos] = min;
}
//删除二叉堆的根,并通过上移使得新得到的序列仍为二叉堆
void DeleteMin(HuffmanTree t, int len)
{
struct hNode last = t[len--];//二叉堆的最后一个元素
int child, pos = 1;
while (pos * 2 <= len) //把二叉堆的某些元素往前移,使得新得到的序列仍为二叉堆
{
child = pos * 2;
if (child != len && t[child+1].weight < t[child].weight) //若i有右儿子,且右儿子小于左儿子,c指向右儿子
child++;
if (last.weight > t[child].weight) //若i的小儿子小于二叉堆的最后一个元素,把其移到i的位置
t[pos] = t[child];
else
break;
pos = child;
}
t[pos] = last; //把二叉堆的最后一个元素放到适当的空位,此时得到的序列仍为二叉堆
}
//把x插入到原长度为len-1的二叉堆
void InsertHfNode(HuffmanTree t, int len, struct hNode x)
{
int i;
for (i=len; i/2>0 && t[i/2].weight>x.weight; i/=2)
t[i] = t[i/2];
t[i] = x;
}
//先序遍历赫夫曼树,累计非叶子结点的值
void Preorder(HuffmanTree t, int p, unsigned long long & sum)
{
if (t[p].lc > 0)
{
sum += t[p].weight;
Preorder(t, t[p].lc, sum); //遍历左子树
Preorder(t, t[p].rc, sum); //遍历右子树
}
}
PASCAL代码:
PROGRAM P1097 (input, output);
TYPE
HuffmanTree = record
weight : longword;
lc, rc : integer;
end;
CONST
MAX = 10000;
VAR
hT : array [1..MAX*2] of HuffmanTree;
w : array [1..MAX] of longword;
i,n : integer;
sum : longword;
PROCEDURE BuildHeap(len : integer); FORWARD;
PROCEDURE PercDown(pos, len : integer); FORWARD;
PROCEDURE DeleteMin(len : integer); FORWARD;
PROCEDURE InsertHfHuffmanTree(len : integer; x : HuffmanTree); FORWARD;
{创建一棵赫夫曼树}
PROCEDURE CreateHuffmanTree(n : integer);
var
i, left, right : integer;
add : HuffmanTree;
begin
for i:=1 to n do {初始化赫夫曼树}
begin
hT[i].weight := w[i];
hT[i].lc := 0;
hT[i].rc := 0;
end; {for}
BuildHeap(n); {构造一个二叉堆;小顶堆}
left := n;
right := n;
while left > 1 do {此处通过减小和增大left的值来改变二叉堆的大小}
begin
inc(right);
hT[right] := hT[1];
add.weight := hT[1].weight;
add.lc := right; {存储左孩子下标}
DeleteMin(left);
hT[left] := hT[1];
add.weight := add.weight + hT[1].weight;
add.rc := left; {存储右孩子下标}
dec(left);
DeleteMin(left);
InsertHfHuffmanTree(left, add);
end; {while}
end; {CreateHuffmanTree}
{构造一个二叉堆;小顶堆}
PROCEDURE BuildHeap(len : integer);
var
i : integer;
begin
for i:=len div 2 downto 1 do
PercDown(i, len);
end; {BuildHeap}
{构造二叉堆的功能子函数}
PROCEDURE PercDown(pos, len : integer);
var
child : integer;
min : HuffmanTree;
begin
min := hT[pos];
while (pos * 2 ) <= len do
begin
child := pos * 2;
if (child <> len) and (hT[child+1].weight < hT[child].weight) then
inc(child);
if min.weight > hT[child].weight then
begin
hT[pos] := hT[child];
pos := child;
end {if}
else
len := pos; {此语句的目的是为了跳出循环}
end; {while}
hT[pos] := min;
end; {PercDown}
{删除二叉堆的根,并通过上移使得新得到的序列仍为二叉堆}
PROCEDURE DeleteMin(len : integer);
var
child, pos : integer;
last : HuffmanTree;
begin
pos := 1;
last := hT[len];
dec(len);
while (pos * 2) <= len do {把二叉堆的某些元素往前移,使得新得到的序列仍为二叉堆}
begin
child := pos * 2;
if (child <> len) and (hT[child+1].weight < hT[child].weight) then {若i有右儿子,且右儿子小于左儿子,c指向右儿子}
inc(child);
if last.weight > hT[child].weight then {若i的小儿子小于二叉堆的最后一个元素,把其移到i的位置}
begin
hT[pos] := hT[child];
pos := child;
end {if}
else
len := pos; {此语句的目的是为了跳出循环}
end; {while}
hT[pos] := last; {把二叉堆的最后一个元素放到适当的空位,此时得到的序列仍为二叉堆}
end; {DeleteMin}
{把x插入到原长度为len-1的二叉堆}
PROCEDURE InsertHfHuffmanTree(len : integer; x : HuffmanTree);
var
i : integer;
begin
i := len;
while (i div 2 > 0) and (hT[i div 2].weight > x.weight) do
begin
hT[i] := hT[i div 2];
i := i div 2;
end; {while}
hT[i] := x;
end; {BuildHeap}
{先序遍历赫夫曼树,累计非叶子结点的值}
PROCEDURE Preorder(var sum : longword; p : integer);
begin
if hT[p].lc > 0 then
begin
sum := sum + hT[p].weight;
Preorder(sum, hT[p].lc); {遍历左子树}
Preorder(sum, hT[p].rc); {遍历右子树}
end; {if}
end; {Preorder}
BEGIN
read(n);
for i:=1 to n do
read(w[i]);
CreateHuffmanTree(n);
sum := 0;
Preorder(sum, 1); {先序遍历赫夫曼树,累计非叶子结点的值}
writeln(sum);
END.
方法二:队列:
c++代码:
#include
using namespace std;
const int MAX = 20000;
int MergeApple(int a[], int n);
void Insert(int oldQueue[], int n, int data);
int main(int argc, char* argv[])
{
int n;
cin >> n;
int *a = new int[n];
for (int i=0; i cin >> a[i];
cout << MergeApple(a, n) << endl;
system("pause");
return 0;
}
int MergeApple(int a[], int n)//按增序插入数组
{
if (n == 1)
return a[0];
if (n == 2)
return a[0] + a[1];
int *oldQueue = new int[n];
int *newQueue = new int[n];
for (int i=0; i {
Insert(oldQueue, i, a[i]); //按增序插入数组
}
int frontOld, frontNew, rearNew;
long long sum = 0;
//先加上前两个
frontOld = frontNew = rearNew = 0;
newQueue[rearNew++] = oldQueue[frontOld] + oldQueue[frontOld+1];
frontOld += 2;
while (frontOld < n)//合并oldQueue的所有元素
{
if (oldQueue[frontOld] > newQueue[frontNew])
sum = newQueue[frontNew++];
else
sum = oldQueue[frontOld++];
if (frontNew < rearNew && frontOld < n)
{
if (oldQueue[frontOld] > newQueue[frontNew])
sum += newQueue[frontNew++];
else
sum += oldQueue[frontOld++];
}
else if (frontNew < rearNew)
sum += newQueue[frontNew++];
else if (frontOld < n)
sum += oldQueue[frontOld++];
newQueue[rearNew++] = sum;
}
while (frontNew < rearNew - 1)//如果newQueue的元素没有合并完
{
newQueue[rearNew++] = newQueue[frontNew] + newQueue[frontNew+1];
frontNew += 2;
}
sum = 0;
for (int i=0; i sum += newQueue[i];
return sum;
}
//按增序插入数组
void Insert(int oldQueue[], int n, int data)
{
int mid, left = 0, right = n - 1;
if (n == 0 || data >= oldQueue[right]) //最大
{
oldQueue[n] = data;
return ;
}
else if (data < oldQueue[left]) //最小
{
for (int i=right; i>=0; i--)
{
oldQueue[i+1] = oldQueue[i];
}
oldQueue[0] = data;
return ;
}
while (left <= right)
{
mid = (left + right) / 2;
if (oldQueue[mid] <= data)
{
left = mid + 1;
}
else if (oldQueue[mid] > data)
{
right = mid - 1;
}
}
if (oldQueue[mid] < data)
mid++;
//插入到a[mid]之前
for (int i=n-1; i>=mid; i--)
{
oldQueue[i+1] = oldQueue[i];
}
oldQueue[mid] = data;
}
long long MergeApple(int a[], int n) //基数排序
{
if (n == 1)
return a[0];
if (n == 2)
return a[0] + a[1];
int *oldQueue = new int[n];
int *newQueue = new int[n];
//基数排序
int lib[MAX+1] = {0};
for (int i=0; i lib[a[i]]++;
n = 0;
for (int i=0; i<=MAX; i++)
{
while (lib[i] > 0)
{
oldQueue[n++] = i;
lib[i]--;
}
}
int frontOld, frontNew, rearNew;
long long sum = 0;
//先加上前两个
frontOld = frontNew = rearNew = 0;
newQueue[rearNew++] = oldQueue[frontOld] + oldQueue[frontOld+1];
frontOld += 2;
while (frontOld < n)
{
if (oldQueue[frontOld] > newQueue[frontNew])
sum = newQueue[frontNew++];
else
sum = oldQueue[frontOld++];
if (frontNew < rearNew && frontOld < n)
{
if (oldQueue[frontOld] > newQueue[frontNew])
sum += newQueue[frontNew++];
else
sum += oldQueue[frontOld++];
}
else if (frontNew < rearNew)
sum += newQueue[frontNew++];
else if (frontOld < n)
sum += oldQueue[frontOld++];
newQueue[rearNew++] = sum;
}
while (frontNew < rearNew - 1)
{
newQueue[rearNew++] = newQueue[frontNew] + newQueue[frontNew+1];
frontNew += 2;
}
sum = 0;
for (int i=0; i sum += newQueue[i];
return sum;
}
PASCAL代码:
PROGRAM MergeApple(INPUT, OUTPUT);
CONST
MAX = 20000;
TYPE
arr = array [1..10000] of longword;
VAR
a : arr;
i, n : integer;
FUNCTION Merge(a : arr; n : integer): LongWord;
var
lib : array [1..MAX] of integer;
oldQueue, newQueue : arr;
i, frontOld, frontNew, rearNew : integer;
sum : LongWord;
begin
if n = 1 then
Merge := a[1]
else if n = 2 then
Merge := a[1] + a[2]
else
begin
//基数排序
for i:=1 to MAX do
lib[i] := 0;
for i:=1 to n do
inc(lib[a[i]]);
n := 0;
for i:=1 to MAX do
while lib[i] > 0 do
begin
inc(n);
oldQueue[n] := i;
dec(lib[i]);
end; {while}
frontOld := 1;
frontNew := 1;
rearNew := 1;
{先加上前两个}
newQueue[rearNew] := oldQueue[frontOld] + oldQueue[frontOld+1];
inc(frontOld, 2);
while frontOld <= n do
begin
if oldQueue[frontOld] > newQueue[frontNew] then
begin
sum := newQueue[frontNew];
inc(frontNew);
end {if}
else
begin
sum := oldQueue[frontOld];
inc(frontOld);
end; {if}
if (frontNew <= rearNew) and (frontOld <= n) then
begin
if oldQueue[frontOld] > newQueue[frontNew] then
begin
sum := sum + newQueue[frontNew];
inc(frontNew);
end {if}
else
begin
sum := sum + oldQueue[frontOld];
inc(frontOld);
end; {if}
end {if}
else if frontNew <= rearNew then
begin
sum := sum + newQueue[frontNew];
inc(frontNew);
end {else if}
else
begin
sum := sum + oldQueue[frontOld];
inc(frontOld);
end; {else if}
inc(rearNew);
newQueue[rearNew] := sum;
end; {while}
while frontNew < rearNew do
begin
inc(rearNew);
newQueue[rearNew] := newQueue[frontNew] + newQueue[frontNew+1];
inc(frontNew, 2);
end; {while}
sum := 0;
for i:=1 to rearNew do
inc(sum, newQueue[i]);
Merge := sum;
end; {else}
end; {Merge}
BEGIN
read(n);
for i:=1 to n do
read(a[i]);
writeln(Merge(a, n));
END.