【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; }
苟利国家生死以, 岂因祸福避趋之。