哈夫曼树(POJ3253)

题目

POJ3253

大概题意:XX想修补东西,然后需要用木板。可以认为XX有一根无限长(长度=他需要的木板长度总和)的木板,但是他需要N块长度为Li的木板,所以他需要把这块无限长的木板锯成他需要的。每次锯木板的花费与锯之前木板的长度相等。求最小花费!
测试案例:
3
8
5
8

34

sum=0;
开始木板长度21(8+5+8),sum+=21,要锯成13+8;
然后,需要锯的木板长度13,sum+=13,要锯成5+8;
所以sum=34.
(写代码时可以将过程反过来。)

如何确定每次锯的木板的长度,用到的就是“哈夫曼树”的思想。每次取最优,又符合贪心的思想。

但是仅仅是这样,会超时的。

第一步

我用的数组存的所有的数,每次都排序完,取前两个数的和。
毫无疑问超时。O(n^2logn)
TLE

第二步

我将数组改成链表,使用插入排序,以为可以优化.
但是,还是超时。O(n^2)

代码(TLE)

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include <set>
#include <map>
#include<list>
#include <stack>
#include <queue>
#include <vector>
#include <ctime>

#define ll long long
#define f(i,a,b) for(int i=a;i<=b;i++)
#define m(a,b) memset(a,b,sizeof(a))
#define MAX 0x3f3f3f3f
const ll MOD=1e+9+7;

using namespace std;
struct Node{
    ll data;
    Node* next;
};
typedef Node* linklist;
linklist head;
void assert(ll n)
{
    linklist p=head;
    linklist q=new(Node);
    if(p->next==NULL){
        q->data=n;
        q->next=NULL;
        p->next=q;
    }
    else if(n<=p->next->data){
        q->data=n;
        q->next=p->next;
        p->next=q;
    }
    else {
        p=p->next;
        bool flag=1;
        while(p->next!=NULL){
            if(n<=p->next->data){
                q->data=n;
                q->next=p->next;
                p->next=q;
                flag=0;
                break;
            }
            p=p->next;
        }
        if(flag){
            q->data=n;
            q->next=NULL;
            p->next=q;
        }
    }
}
void del(linklist p)
{
    linklist q=p->next;
    p->next=q->next;
    delete(q);
}
ll operate()
{
    ll temp=head->next->data+head->next->next->data;
    del(head);
    del(head);
    assert(temp);
    return temp;
}

int main()
{
    head=new(Node);
    head->next=NULL;
    int n;
    cin>>n;
    f(i,0,n-1){
        ll a;
        cin>>a;
        assert(a);
    }
    ll ans=0;
    f(i,0,n-2){
        ans+=operate();
    }
    cout<<ans<<endl;
    return 0;
}

第三步

我以为我以为就是我以为的。。。
看了别人的代码,才知道,问题处在了别的地方,但是说实话,我感觉只是测试数据不够强,不然代码同样会被卡死。为什么这么说呢,请看我改过之后的AC代码。目前对标程了解尚浅。。。

AC代码

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include <set>
#include <map>
#include<list>
#include <stack>
#include <queue>
#include <vector>
#include <ctime>

#define ll long long
#define f(i,a,b) for(int i=a;i<=b;i++)
#define m(a,b) memset(a,b,sizeof(a))
#define MAX 0x3f3f3f3f
const ll MOD=1e+9+7;

using namespace std;
struct Node{
    ll data;
    Node* next;
};
typedef Node* linklist;
linklist head;
void assert(ll n)
{
    linklist p=head;
    linklist q=new(Node);
    if(p->next==NULL){
        q->data=n;
        q->next=NULL;
        p->next=q;
    }
    else if(n<=p->next->data){
        q->data=n;
        q->next=p->next;
        p->next=q;
    }
    else {
        p=p->next;
        bool flag=1;
        while(p->next!=NULL){
            if(n<=p->next->data){
                q->data=n;
                q->next=p->next;
                p->next=q;
                flag=0;
                break;
            }
            p=p->next;
        }
        if(flag){
            q->data=n;
            q->next=NULL;
            p->next=q;
        }
    }
}
void del(linklist p)
{
    linklist q=p->next;
    p->next=q->next;
    delete(q);
}
ll operate()
{
    ll temp=head->next->data+head->next->next->data;
    del(head);
    del(head);
    assert(temp);
    return temp;
}

int main()
{
    head=new(Node);
    head->next=NULL;
    int n;
    scanf("%d",&n);
    int s[20020]; f(i,0,n-1){ scanf("%d",&s[i]); } sort(s,s+n); linklist p=head; f(i,0,n-1){ linklist q=new(Node); q->data=s[i]; q->next=NULL; p->next=q; p=q; } ll ans=0; f(i,0,n-2){ ans+=operate(); } cout<<ans<<endl; return 0; }

看出来我该那里了么?

就是开始读入数据构造链表的时候。
代码TLE,直接就是输入一个插入一个,时间复杂度最坏是O((N^2)/2),AC代码,将这个构造链表的过程优化到了O(N*logN);
至于,主要的算法实现的过程,倒是没卡O((N^2)/2).
不清楚呀,为什么?
可能是,两个时间复杂度O(输入+插入)

第四步

学习别人代码:

  1. 优先队列的构造使用
    《待补充》

  2. STL中优先队列的使用
    《待补充》

STL代码

/*STL 优先队列*/

//Memory Time
//512K 47MS

#include<iostream>
#include<vector>
#include<queue>
#include<cstdio>
using namespace std;

//比较规则,最小优先
class cmp
{
public:
    bool operator()(const __int64 a,const __int64 b)const
    {
        return a>b;
    }
};

int main(void)
{
    int n;  //需要切割的木板个数
    while(cin>>n)
    {
        priority_queue<__int64,vector<__int64>,cmp>Queue;  //定义优先队列

        for(int i=1;i<=n;i++)
        {
            __int64 temp;
            scanf("%I64d",&temp);
            Queue.push(temp);       //输入要求的木板长度(费用)并入队
        }

        __int64 mincost=0;   //最小费用
        while(Queue.size()>1)  //当队列中小于等于一个元素时跳出
        {
            __int64 a=Queue.top();  //得到队首元素的值,并使其出队
            Queue.pop();
            __int64 b=Queue.top();  //两次取队首,即得到最小的两个值
            Queue.pop();

            Queue.push(a+b);  //入队
            mincost+=a+b;
        }

        printf("%I64d\n",mincost);

        while(!Queue.empty())  //清空队列
            Queue.pop();
    }
    return 0;
}

Finally

个人感觉,利用堆,才是AC这道题的最好办法。上述AC代码,过的并不能说服人,凑巧罢了。
利用堆,能将哈夫曼算法实现部分的时间复杂度优化到O(nlogn).

学习,如何构造堆,利用堆来实现哈夫曼树。

#include <iostream>
using namespace std;
long long n,i,ans,p[20001];
void heap(long x,long y)
{
  long i,j,temp;
  temp=p[x];
  i=x;
  j=i*2;
  while(j<=y)
  {
    if(j<y&&p[j+1]<p[j])
      j++;
    if(temp>p[j])
    {
      p[i]=p[j];
      i=j;
      j=i*2;             
    }       
    else
      break;    
  }     
  p[i]=temp;
}
int main()
{
  cin>>n;
  ans=0;
  for(i=1;i<=n;i++)
    cin>>p[i];
  for(i=n/2;i>=1;i--)
    heap(i,n);
  while(n>1)
  {
    p[0]=p[1];
    p[1]=p[n--];
    heap(1,n);
    p[1]+=p[0];
    ans+=p[1];
    heap(1,n);          
  }  
  cout<<ans<<endl;
  return 0;
}

你可能感兴趣的:(哈夫曼树,poj3253)