zoukankan      html  css  js  c++  java
  • 一.堆的性质

    堆是一颗完全二叉树

    堆顶一定是优先级“最大”,最小”

    堆一般有两种——小根堆和大根堆,对于大根堆而言,任何一个非根节点,它的优先级都小于堆顶,对于小根堆而言,任何一个非根节点,它的优先级都大于堆顶

    堆一般是用二叉树来实现的

    一颗完全二叉树,每个节点有一个权值。

    父节点的权值总是大于等于(或小于等于)两个子节点的权值。

    2.复杂度

    O(1)的时间查询最大(小)值,直接取堆顶。

    O(logn)的时间删除最大(小)值。

    O(logn)的时间插入元素。

    O(n)的时间整体建堆。

    3.实现

    插入:先放到数组末端,然后不满足堆的性质就向上交换

    删堆顶:交换堆顶和堆末,然后直接size--,接下来让堆满足性质

    查堆顶:输出h[1]

    洛谷模板

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int N=2e6;
    int h[N],size;//heap
    void push(int x) {
        h[++size]=x;
        int now=size;
        while(now){
            int nxt=now>>1;
            if(h[nxt]>h[now]) {
                swap(h[nxt],h[now]);
                now=nxt;
            }
            else break;
        }
    }
    void pop() {
        swap(h[size],h[1]);size--;
        int now=1;
        while((now<<1)<=size) {
            int nxt=now<<1;
            if(nxt+1<=size && h[nxt+1]<h[nxt]) nxt++;
            if(h[nxt]<h[now]) {
                swap(h[now],h[nxt]);
                now=nxt;
            }
            else break;
        }
    }
    int n;
    int main(){
        scanf("%d",&n);
        for(int i=1,op,x;i<=n;i++) {
            scanf("%d",&op);
            if(op==1) {scanf("%d",&x);push(x);}
            if(op==2) printf("%d
    ",h[1]);
            if(op==3) pop();
        }
        return 0;
    }
    

    4.STL

    STL只支持删除堆顶,而不支持删除其他元素

    我们可以另外开一个一样的堆称为垃圾堆,每次删除一个元素,就把这个元素放入垃圾堆中,在对原堆做任何操作时,都首先检查堆顶元素与垃圾堆的堆顶元素是否相同,如果相同两者一起删掉,直到不相同或垃圾堆为空为止

    5.例题

    合并果子

    裸题。。。

    实践证明手写堆比STL快一半

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int N=2e6;
    int h[N],size;//heap
    void push(int x) {
        h[++size]=x;
        int now=size;
        while(now){
            int nxt=now>>1;
            if(h[nxt]>h[now]) {
                swap(h[nxt],h[now]);
                now=nxt;
            }
            else break;
        }
    }
    void pop() {
        swap(h[size],h[1]);size--;
        int now=1;
        while((now<<1)<=size) {
            int nxt=now<<1;
            if(nxt+1<=size && h[nxt+1]<h[nxt]) nxt++;
            if(h[nxt]<h[now]) {
                swap(h[now],h[nxt]);
                now=nxt;
            }
            else break;
        }
    }
    int n,ans;
    int main(){
        scanf("%d",&n);
        for(int i=1,x;i<=n;i++) {
            scanf("%d",&x);
            push(x);
        }
        for(int i=1;i<n;i++){
            int x=h[1];pop();
            int y=h[1];pop();
            push(x+y);
            ans+=x+y;
        }
        printf("%d
    ",ans);
        return 0;
    }
    

    当年的...

    #include<bits/stdc++.h>
    using namespace std;
    priority_queue< int,vector<int>,greater<int> > q;
    int n,a,ans=0,b;
    int main(){
    	ios::sync_with_stdio(false);
    	cin>>n;
    	for(int i=1;i<=n;i++) {
    		cin>>a;
    		q.push(a);
    	}
    	for(int i=1;i<n;i++) {
    		a=q.top();q.pop();
    		b=q.top();q.pop();
    		ans+=a+b;
    		q.push(a+b);
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

    中位数

    这题看起来实在是。。。不像堆

    真的没看出正解。。。正解实在巧妙

    对顶堆

    使用两个堆,大根堆维护较小的值,小根堆维护较大的值
    即小根堆的堆顶是较大的数中最小的,大根堆的堆顶是较小的数中最大的
    将大于大根堆堆顶的数(比所有大根堆中的元素都大)的数放入小根堆,小于等于大根堆堆顶的数(比所有小根堆中的元素都小)的数放入大根堆
    那么就保证了所有大根堆中的元素都小于小根堆中的元素
    于是我们发现对于大根堆的堆顶元素,有【小根堆的元素个数】个元素比该元素大,【大根堆的元素个数-1】个元素比该元素小;
    同理,对于小跟堆的堆顶元素,有【大根堆的元素个数】个元素比该元素小,【小根堆的元素个数-1】个元素比该元素大;
    那么维护【大根堆的元素个数】和【小根堆的元素个数】差值不大于1之后,元素个数较多的堆的堆顶元素即为当前中位数;(如果元素个数相同,那么就是两个堆堆顶元素的平均数,本题不会出现这种情况)
    根据这两个堆的定义,维护方式也很简单,把元素个数多的堆的堆顶元素取出,放入元素个数少的堆即可

    啊疯了手写堆怎么也过不去

    #include <iostream>
    #include <cstdio>
    using namespace std;
    const int N=100005;
    int h[N],H[N];
    int size_big,size_small;
    int n;
    void push_big(int x) {
    	H[++size_big]=x;
    	int now=size_big;
    	while(now){
    		int nxt=now>>1;
    		if(H[nxt]<H[now]){
    			swap(H[nxt],H[now]);
    			now=nxt;
    		}else break;
    	}
    }
    void pop_big () {
    	swap(H[1],H[size_big]);size_big--;
    	int now=1;
    	while((now<<1)<=size_big){
    		int nxt=now<<1;
    		if(H[nxt+1]>H[nxt]&&nxt+1<=size_big) nxt++;
    		if(H[nxt]>H[now]) {
    			swap(H[nxt],H[now]);
    			now=nxt;
    		}else break;
    	}
    }
    void push_small(int x) {
    	h[++size_small]=x;
    	int now=size_small;
    	while(now){
    		int nxt=now>>1;
    		if(h[nxt]>h[now]) {
    			swap(h[nxt],h[now]);
    			now=nxt;
    		}else break;
    	}
    }
    void pop_small() {
    	swap(h[1],h[size_small]);size_small--;
    	int now=1;
    	while((now<<1)<=size_small){
    		int nxt=now<<1;
    		if(h[nxt+1]<h[nxt]&&nxt+1<=size_small) nxt++;
    		if(h[nxt]<h[now]) {
    			swap(h[nxt],h[now]);
    			now=nxt;
    		}else break;
    	}
    }
    int abs(int a,int b){
    	return a>b?(a-b):(b-a);
    }
    int main(){
    	scanf("%d",&n);
    	scanf("%d",&h[1]);
    	printf("%d
    ",h[1]);
    	for(int i=2,x;i<=n;i++) {
    		scanf("%d",&x);
    		if(x>H[1]) push_small(x);
    		else push_big(x);
    		while(abs(size_small,size_big)>1)
    			if(size_small<size_big) {push_small(H[1]);pop_big();}
    			else {push_big(h[1]);pop_small();}
    		if(i&1)  printf("%d
    ",size_big>size_small?H[1]:h[1]);
    	}
    	return 0;
    }
    
    #include <iostream>
    #include <cstdio>
    #include <vector>
    #include <queue>
    using namespace std;
    int n,x;
    priority_queue < int,vector<int> > Q;
    priority_queue < int,vector<int>,greater<int> >q;
    vector<int>a;
    int abs(int a,int b) {
    	return a>b?(a-b):(b-a);
    }
    int main() {
    	scanf("%d",&n);
    	scanf("%d",&x);
    	Q.push(x);
    	printf("%d
    ",x);
    	for(int i=2;i<=n;i++) {
    		scanf("%d",&x);
    		if(x>Q.top()) q.push(x);
    		else Q.push(x);
    		while(abs(Q.size(),q.size())>1) {
    			if(Q.size()>q.size()) {q.push(Q.top()); Q.pop();}
    			else {Q.push(q.top()); q.pop();}
    		}
    		if(i&1) printf("%d
    ",Q.size()>q.size()?Q.top():q.top());
    	}
        return 0;
    }
    

    还可以vector 水过

    #include<bits/stdc++.h>
    using namespace std;
    int n,x,ans=0;
    vector<int>a;
    int main() {
        ios::sync_with_stdio(false);
        cin>>n;
        for(int i=1;i<=n;i++) {
            cin>>x;
            a.insert(upper_bound(a.begin(),a.end(),x),x);
            if(i%2==1)
            	cout<<a[(i-1)/2]<<endl;
        }
        return 0;
    }
    

    Work Scheduling G

    这道题读题就理解错了。。。

    对于一个时间,可以干截止时间在这之前的事,

    比较容易看出的贪心——肯定价值越大越要选,

    那么这是个带反悔的贪心

    能选的时候都先选上,然后把他的价值装进小根堆,

    然后当这个截止时间已经被选过,就从小根堆里把最小的(堆顶)扔了,选上当前的

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <vector>
    #include <queue>
    using namespace std;
    inline int read(){
    	int x=0;char ch=getchar();
    	while (!isdigit(ch)) ch=getchar();
    	while (isdigit(ch)) {x=x*10+ch-'0'; ch=getchar();}
    	return x;
    }
    int n,x;
    struct node{
    	int time,p;
    	bool operator < (const node &x) const {
    		return time<x.time;
    	}
    }a[100005];
    priority_queue < int,vector<int>,greater<int> > Q;
    long long ans;
    int main() {
    	n=read();
    	for(int i=1;i<=n;i++) {
    		a[i].time=read();a[i].p=read();
    	}
    	sort(a+1,a+1+n);
    	for(int i=1;i<=n;i++) {
    		if(a[i].time>Q.size()){
    			Q.push(a[i].p);
    			ans+=a[i].p;
    		} else {
    			if(a[i].p>Q.top()){
    				ans-=Q.top();Q.pop();
    				ans+=a[i].p; Q.push(a[i].p);
    			}
    		}
    	}
    	printf("%lld
    ",ans);
        return 0;
    }
    

    黑匣子

    啊这题毒瘤,愣是想不出正解,刚学平衡树,so写了个二叉查找树,然鹅最后一个点T飞

    #include <iostream>
    #include <cstdio>
    using namespace std;
    inline int read(){
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    const int N=500005;
    const int INF=2147483647;
    int n,m,tree_cnt;
    struct TREE{
        int cnt,ls,rs,val,siz;
    }t[N];
    // 插入
    inline void insert(int x,int v){
        t[x].siz++;
        if(t[x].val==v){t[x].cnt++;return;}
        if(t[x].val>v){
             if(!t[x].ls){
                tree_cnt++;
                t[tree_cnt].cnt=t[tree_cnt].siz=1;
                t[tree_cnt].val=v;
                t[x].ls=tree_cnt;
            }else {  insert(t[x].ls,v);  }     
        }else{
            if(!t[x].rs){
                tree_cnt++;
                t[tree_cnt].cnt=t[tree_cnt].siz=1;
                t[tree_cnt].val=v;
                t[x].rs=tree_cnt;
            }else { insert(t[x].rs,v); }
        }
    }
    
    inline int get_val(int x,int rk){
        if(x==0) return INF;
        if(t[t[x].ls].siz>=rk) return get_val(t[x].ls,rk);
        if(t[t[x].ls].siz+t[x].cnt>=rk) return t[x].val;
        return get_val(t[x].rs,rk-t[t[x].ls].siz-t[x].cnt);
    }
    int a[N];
    int main(){
        m=read();n=read();
        int opt,x;
        for(int i=1;i<=m;i++) 
    		a[i]=read();
    	int now=1;
    	for(int i=1,x;i<=n;i++){
    		x=read();
    		while(now<=x) {
    			if(!tree_cnt){
    	            tree_cnt++;
    	            t[tree_cnt].cnt=t[tree_cnt].siz=1;
    	            t[tree_cnt].val=a[now];
    	        }
    	        else insert(1,a[now]);
    			now++; 
    		}
    		printf("%d
    ",get_val(1,i));
        }
        return 0;
    }
    
    

    正解还是两个堆一起搞

    小根堆堆顶维护第 i 小,大根堆放 前 i-1 小

    #include <iostream>
    #include <cstdio>
    #include <queue>
    #include <vector>
    using namespace std;
    inline int read(){
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    const int N=500005;
    int n,m,a[N];
    priority_queue < int,vector<int> > Q;
    priority_queue < int,vector<int>,greater<int> >q;
    int main(){
        m=read();n=read();
        for(int i=1;i<=m;i++) 
    		a[i]=read();
    	int now=1;
    	for(int i=1,x;i<=n;i++){
    		x=read();
    		while(now<=x) {
    			Q.push(a[now]);
    			if(Q.size()==i) {q.push(Q.top());Q.pop();}
    			now++;
    		}
    		printf("%d
    ",q.top());
    		Q.push(q.top());
    		q.pop();
        }
        return 0;
    }
    
    

    BZOJ 5102 Prawnicy

    假如我们已经确定了最终区间的左端点L,那么我们选择的区间一定是左端点在L左边,且右端点最右的K个点。所以我们将所有区间按左端点排序,用小根堆维护左端点在左边,且右端点最大的K个点。每次用第K大值更新答案即可

    https://www.cnblogs.com/CQzhangyu/p/7954179.html

  • 相关阅读:
    3892: [Usaco2014 Dec]Marathon
    3891: [Usaco2014 Dec]Piggy Back
    2016: [Usaco2010]Chocolate Eating
    3016: [Usaco2012 Nov]Clumsy Cows
    3359: [Usaco2004 Jan]矩形
    1593: [Usaco2008 Feb]Hotel 旅馆
    关于ubuntu上无法运行cmd markdown
    Mininet实验 动态改变转发规则
    Mininet简单性能测试
    Mininet实验 基于Mininet测量路径的损耗率
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13524209.html
Copyright © 2011-2022 走看看