一.堆的性质

堆是一颗完全二叉树

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

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

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

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

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

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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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

posted @ 2020-08-18 16:21  ke_xin  阅读(31)  评论(0编辑  收藏  举报