zoukankan      html  css  js  c++  java
  • 二叉堆+哈夫曼树小结

    概念:大根堆:树中任意一个节点的权值都小于父节点的权值,小根堆反之:

    根据完全二叉树的性质:我们储存节点时,左儿子的编号等于父节点编号*2,右儿子编号等于父节点编号*2+1;

    c++的stl中的priority_queue(优先队列)为实现一个大根堆,支持push(insert),top( ),pop( ) ,不支持remove,所以感觉很方便,但还是要会手写堆的;

    第一题:supermarket

    贪心:最优解中,对于每个时间t,应该在保证不卖出过期的情况下尽量卖出利润前t大的商品,动态维护这样一个性质;

    我们将商品时间排序,见一个小根堆,根节点为商品利润,对于每一个商品;若当前过期时间t大于堆中商品个数,直接插入,若等于,若此时商品利润大于堆顶,则替换堆顶;

    #include<bits/stdc++.h>
    using namespace std;
    int n;
    const int maxn = 1e4 + 100;
    template<typename T>inline void read(T &x)
    {
        x=0;T f=1,ch=getchar();
        while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
        while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
        x*=f;
    }
    pair<int,int> a[maxn];
    int main() {
        while(cin>>n) {
            for(int i=1;i<=n;i++) 
                read(a[i].second),read(a[i].first);
            sort(a + 1, a + n + 1);
            priority_queue<int ,vector<int>,greater<int> > q;
            for(int i=1;i<=n;i++) {
                if(a[i].first>q.size()) 
                    q.push(a[i].second);
                else if(a[i].first==q.size()&&a[i].second>q.top()) {
                    int x=q.top();q.pop();
                    q.push(a[i].second);
                }
            }
            int ans=0;
            while(q.size()) {
                ans+=q.top();q.pop();
            }
            cout<<ans<<endl;
        }
        return 0;
    }
    View Code

    第二题:Sequence

    一个M行N列的矩阵,每一行中选一个数,一共取M个,求和,共有NM中结果,输出前N小个数;

    我们可以先做前两个序列一共取两个数得到的和,取前n小,构成一个长度为n序列,然后将得到的新序列与第三个序列合并,以此类推,一共合并M-1次,最后得到的数列就是前n小的和;

    对于一个排好序的数列{an},{bn}(排不排序无所谓);

    得到n个序列:

    b1+a1,b1+a2,b1+a3.....,b1+an;

    b2+a1,b2+a2,b2+a3.....,b2+an;

    b3+a1,b3+a2,b3+a3.....,b3+an;

    b4+a1,b4+a2,b4+a3.....,b4+an;

    b5+a1,b5+a2,b5+a3.....,b5+an;

    、、

    bn+a1,bn+a2,bn+a3.....,bn+an;

    我们可以开一个小根堆,加入两个元素,一个为权值,一个为当前a的编号;

    对于上部分红色部分:我们可以发现它是当前n序列中的最小值,因为an是排好序的,我们将a1+b[i]先加入小根堆中;

    假定我们选定b2+a1,为当前第二小,那么我们删掉b2+a1,将它所对应的下一个数b2+a2,加入堆中,进入第三小的选择中,从而保证小根堆中一直只有n个值

    每次取出堆顶,那么当前的权值和赋给一个新的数组c,将下一个数加入堆中,我么可以发现下一个元素的取值和为当前的权值和-当前a[p]+a[p+1};

    将得到的新数组c赋给a,接着向下一个数组合并;最后a数组存的n个数就是最后的答案;

    #include<bits/stdc++.h>
    using namespace std;
    #define N 2010
    typedef pair<int,int> PII;
    template<typename T>inline void read(T &x) {
        x=0; T f=1,ch=getchar();
        while(!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
        while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48); ch=getchar();}
        x*=f;
    }
    
    
    int n,m,T,a[N],b[N],c[N];
    
    inline void Union() {
        priority_queue<PII, vector<PII>, greater<PII> > heap;
        for(int i=1;i<=n;i++) heap.push({a[1]+b[i],1});
        for(int i=1;i<=n;i++) {
            PII t=heap.top();
            heap.pop();
            c[i]=t.first;
            heap.push({t.first+a[t.second+1]-a[t.second],t.second+1});
        }
        for(int i=1;i<=n;i++) a[i]=c[i];
    }
    
    int main() {
        read(T);
        while(T--) {
            read(m); read(n);
            for(int i=1;i<=n;i++) read(a[i]);
            sort(a+1,a+n+1);
            for(int i=1;i<m;i++) {
                for(int j=1;j<=n;j++) read(b[j]);
                sort(b+1,b+n+1);
                Union();
            }
            for(int i=1;i<=n;i++) printf("%d ",a[i]);
            puts(" ");
        }
        return 0;
    }
    View Code

    哈夫曼树:

    名字挺高级哈,对于一个有i个子节点的k叉树,每个节点对应一个权值wi,要求最小化WPL=Σwi * Li,Li表示该节点到根节点的距离;

    为了最小化,我们对于权值大的,尽可能深度较小;

    当k==2,我们可以用贪心建出这样一棵树;

    1.建一个小根堆,存进去n个节点的权值;

    2.取出最小的两个w1,w2,ans+=w1+w2;

    3.建一个权值为w1+w2的节点p,并将p插入小根堆中;

    4.重复直至堆大小为1,此时ans=Σwi * Li,并且为最小值;

    对于k>2时,我们考虑当最后一轮节点数不足以选出k个,这不是最优解,所以我们可以补充添加权值为0的节点,使满足(n-1)mod(k-1)==0,这样执行每次从堆中取出k个的贪心就是正确的;

    第三题:合并果子

    最终消耗的的体力为果子的质量*合并它的次数,这是一个哈夫曼问题;

    对于k==2的情况下,按上述操作即可;

    #include<bits/stdc++.h>
    using namespace std;
    priority_queue< int,vector< int >,greater< int > > q;
    int n,a,sum;
    template<typename T>inline void read(T &x)
    {
        x=0;T f=1,ch=getchar();
        while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
        while(isdigit(ch))  {x=(x<<1)+(x<<3)+(ch^48);  ch=getchar();}
        x*=f;
    }
    int main() {
        read(n);
        for(int i=1;i<=n;i++)
            read(a),q.push(a);
        for(int i=1;i<n;i++) {
            int x1=q.top();q.pop();
            int x2=q.top();q.pop();
            sum+=x1+x2;
            q.push(x1+x2);
        }
        cout<<sum<<endl;
        return 0;
    }
    View Code

    第四题:荷马史诗

    为了使任意si不是sj的前缀,为满足这个性质:

    我们可以使用哈弗曼编码( n个节点的哈夫曼树含有2n-1个节点,没有度为1的节点 编码从叶子节点到根节点,译码从根节点到叶子节点。),也称为前缀编码;

    引例:如果有一需传送的电文为 ‘ABACCDA’,我们可以尝试构建一个二进制串表示,为了保证唯一性,我们需要任意si不是sj的前缀;

    我们记录A,B,C,D的出现次数{3,1,2,1};

    采用哈夫曼编码;

    编码: A:0, C:10,  B:110, D:111 。电文 ‘ABACCDA’ 便为 ‘0110010101110’(共 13 位)。

    对于本题:我们将每个单词出现的次数作为wi,求出一个k叉哈夫曼树,

    那么第一问就是要求出所有叶子结点的WPL,第二问求构造的哈夫曼树最深的深度最小;

    第一问就类似合并果子做就可以了,对于第二问,我们只需要在求哈夫曼树时,对于权值相同的点,优先考虑当前深度最小的即可;

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long 
    typedef pair<ll,int> PII;
    priority_queue<PII,vector<PII>,greater<PII> > heap;
    template<typename T>inline void read(T &x) {
        x=0;T f=1,ch=getchar();
        while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
        x*=f;
    }
    int n,k;
    ll x;
    int main() {
        read(n); read(k);
        for(int i=1;i<=n;i++) {
            read(x);
            heap.push(make_pair(x,0));
        }
        while((n-1)%(k-1)) heap.push(make_pair(0,0)),n++;
        ll res=0;
        while(heap.size()>1) {
            int depth=0;
            ll s=0;
            for(int i=1;i<=k;i++) {
                PII t=heap.top();
                heap.pop();
                s+=t.first;
                depth=max(depth,t.second);    
            }
            res+=s;  
            heap.push(make_pair(s,depth+1));
        }
        cout<<res<<endl<<heap.top().second<<endl;
        return 0;
    }
    View Code

    还是要学习:

    你的问题在于读书不多而想的太多。——杨绛。

  • 相关阅读:
    Activiti学习笔记1 — 下载与开发环境的配置
    JavaScript实现本地图片上传前进行裁剪预览
    我国县及县级以上城市编码
    一些小技巧
    NodeJS学习之异步编程
    NodeJS学习之网络操作
    NodeJS学习之文件操作
    Sass和Compass设计师指南
    Sass
    CKEditor配置及使用
  • 原文地址:https://www.cnblogs.com/Tyouchie/p/10997190.html
Copyright © 2011-2022 走看看