哈夫曼树(POJ3253)

来源:互联网 发布:深圳市逻辑思维软件 编辑:程序博客网 时间:2024/06/03 20:13

题目

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 0x3f3f3f3fconst 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 0x3f3f3f3fconst 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;}
0 0
原创粉丝点击