堆
堆
一.堆的性质
堆是一颗完全二叉树
堆顶一定是优先级“最大”,最小”
堆一般有两种——小根堆和大根堆,对于大根堆而言,任何一个非根节点,它的优先级都小于堆顶,对于小根堆而言,任何一个非根节点,它的优先级都大于堆顶
堆一般是用二叉树来实现的
一颗完全二叉树,每个节点有一个权值。
父节点的权值总是大于等于(或小于等于)两个子节点的权值。
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大值更新答案即可