【BZOJ 4198】[Noi2015]荷马史诗 哈夫曼编码

合并果子加强版.......

哈夫曼树是一种特别的贪心算法,它的作用是使若干个点合并成一棵树,每次合并新建一个节点连接两个合并根并形成一个新的根,使叶子节点的权值乘上其到根的路径长的和最短(等价于每次合并的代价是合并根的权值和,求最小代价)。实现过程就是每次合并权值最小的两个节点,具体一下就是建个森林,每次取最小的两个然后权值加和再放入,重复。

他的实际应用就是哈夫曼编码,拓展就是k叉(本题),对于k叉也就是k进制,如果叶子节点不是1+(k-1)*x的形式,那么就加权值为0的点使他变成此种形式,不能到最后一次再加,那样做不是最优树。

关于哈夫曼编码有静态(本题)和动态,并不会动态......

具体实现的话,工程里是循环找最小,oi里是优先队列。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define HoN Heap::
#define make(a,b) ((Heap::V){(a),(b)})
typedef long long LL;
const int N=100010;
namespace Heap{
  struct V{
    LL val;int deep;
    inline friend bool operator <(V a,V b);
  }k[N];
  int len;
  inline bool operator <(V a,V b){
    return a.val<b.val||(a.val==b.val&&a.deep<b.deep);
  }
  inline bool empty(){return len==0;}
  inline V top(){return k[1];}
  inline int size(){return len;}
  inline void pop(){
    k[1]=k[len--];register int now=1;
    while(now<=(len>>1)){
      int next=now<<1;
      if(next<len&&k[next|1]<k[next])next|=1;
      if(k[now]<k[next])return;
      std::swap(k[now],k[next]),now=next;
    }
  }
  inline void push(V key){
    k[++len]=key;register int now=len;
    while(now!=1&&k[now]<k[now>>1])
      std::swap(k[now],k[now>>1]),now>>=1;
  }
}
int n,k;
LL ans;
int main(){
  scanf("%d%d",&n,&k);LL x;
  for(int i=1;i<=n;++i)
    scanf("%lld",&x),HoN push(make(x,1));
  if(k!=2&&n%(k-1)!=1){
    if(n%(k-1)==0)HoN push(make(0,1)),++n;
    else{
      for(int i=1;i<=k-(n%(k-1));++i)
        HoN push(make(0,1));
      n+=n%(k-1);
    }
  }
  int m=k==2?n-1:n/(k-1);
  int max;HoN V use;
  while(m--){
    x=0,max=0;
    for(int i=1;i<=k;++i)
      use=HoN top(),HoN pop(),x+=use.val,max=std::max(max,use.deep);
    ans+=x,HoN push(make(x,max+1));
  }
  printf("%lld\n%d",ans,HoN top().deep-1);
  return 0;
}

 

posted @ 2017-10-07 07:46  TS_Hugh  阅读(226)  评论(0编辑  收藏  举报