P1097合并果子解题报告

  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.

你可能感兴趣的:(Vijos,Guest解题报告)