通过前几天的一次模拟赛,初次见识到了堆的用处,虽然stl里有相应的结构,但不如自己写的灵活性高、速度快。下面附上几个练习。
第三题:有n个函数,每一个函数有一个效果值,这n个函数是环形排列的,然后要求从n个函数中选出m个不相邻的函数使其效果值之和最大。
思路:这个题在测试的时候直接放弃了,想写一个深搜,结果十分钟赤裸裸的写渣了。下午先用了dp,结果第一次只写对了几个点。后来看了dada的思路,明白了一些,换了循环顺序,先循环取几个数,然后从能取的数的位置用二维数组更新到这个位置上取多少个数的值,因为是环形的,所以要做两遍(一遍是1~n-1,一遍是2~n),然后取较大值输出。
终于弄明白了这道题的正解,被dada神称作贪心的算法。题解里面说用到了残余流的思想,可我根本不会。。。这道题用堆来维护,保存一个值的左右邻结点(一开始就是数组中相邻的两个,但后来不断有根取走后,就是数组中相邻的有值的位置了)和一个值相对应的在堆中的位置(堆用完全二叉树建)。每次取走大根堆的根,然后把这个节点的值更新成它的左右邻结点的值的和-这个点的值(因为我们只能取最早数组中不相邻的两个点,这样如果取走了大根堆的根是由上一个根的左右邻结点更新的,我们就会把第一个根的信息抛掉,正好我们也不再需要这个结点了),再将左右邻结点从堆中删掉(赋成极小值,然后down下去),这样只要取m次根的和就是答案了。
第一次练习了堆,觉得是个很实用的东西。
cogs245促销活动
题目大意:给出n天的购物小票,每天从所有的小票中抽出面值最大和最小的,发出奖金为这两个数之差,其余的进入下一天。输出n天后总共的奖金数。
思路:建了两套堆,一个大根堆一个小根堆,同时用映射保存数在堆中的位置,每次修改都将大根和小根的值取出,为了好写,将这些值赋为无穷小或无穷大,然后down一下就可以了。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int heapin[1000001]={0},heapax[1000001]={0},ain[1000001]={0},aax[1000001]={0}, tot=0,posin[1000001]={0},posax[1000001]={0}; void upin(int x) { while(x>1) { if (ain[heapin[x]]<ain[heapin[x/2]]) { swap(heapin[x],heapin[x/2]); swap(posin[heapin[x]],posin[heapin[x/2]]); } else return; x=x/2; } } void upax(int x) { while(x>1) { if (aax[heapax[x]]>aax[heapax[x/2]]) { swap(heapax[x],heapax[x/2]); swap(posax[heapax[x]],posax[heapax[x/2]]); } else return; x=x/2; } } void downin(int x) { int i; while(x*2<=tot) { if((x*2==tot)||(x*2<tot&&ain[heapin[x*2]]<ain[heapin[x*2+1]])) i=x*2; else i=x*2+1; if(ain[heapin[x]]>ain[heapin[i]]) { swap(heapin[x],heapin[i]); swap(posin[heapin[x]],posin[heapin[i]]); } else return; x=i; } } void downax(int x) { int i; while(x*2<=tot) { if((x*2==tot)||(x*2<tot&&aax[heapax[x*2]]>aax[heapax[x*2+1]])) i=x*2; else i=x*2+1; if(aax[heapax[x]]<aax[heapax[i]]) { swap(heapax[x],heapax[i]); swap(posax[heapax[x]],posax[heapax[i]]); } else return; x=i; } } int main() { freopen("prom.in","r",stdin); freopen("prom.out","w",stdout); int i,j,k,n,t; long long ans=0; scanf("%d",&n); for(i=1;i<=n;++i) { scanf("%d",&k); for(j=1;j<=k;++j) { scanf("%d",&t); ++tot;heapin[tot]=heapax[tot]=tot;ain[tot]=aax[tot]=t; posin[tot]=posax[tot]=tot; upin(tot);upax(tot); } j=heapin[1];t=heapax[1]; ans+=aax[t]-ain[j]; ain[j]=ain[t]=2100000000; aax[j]=aax[t]=-2100000000; downin(1);downin(posin[t]); downax(1);downax(posax[j]); } printf("%lld\n",ans); fclose(stdin); fclose(stdout); }
codevs1245最小的N个和
题目大意:给出n个数的a、b两个集合,从ab中各选一个数,共有n^2个和,找出最小的n个和。
思路:我们将b数组排升序,然后将a中的每一个数指向b数组的第一个数,将和放入堆,每次取走根的元素,给相应的a数组到b数组的映射加1,然后把新的和放入堆中,维护一下,n此操作后就可以了。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int a[100001]={0},b[100001]={0},key[100001]={0},n,heap[100001]={0},pos[100001]={0},c[100001]={0}; void up(int x) { while(x>1) { if (c[heap[x]]<c[heap[x/2]]) { swap(heap[x],heap[x/2]); swap(pos[x],pos[x/2]); } x=x/2; } } void down(int x) { int i; while(x*2<=n) { if ((x*2==n)||(x*2<n&&c[heap[x*2]]<c[heap[x*2+1]])) i=x*2; else i=x*2+1; if (c[heap[x]]>c[heap[i]]) { swap(heap[x],heap[i]); swap(pos[x],pos[i]); } x=i; } } int main() { int i,j; scanf("%d",&n); for (i=1;i<=n;++i) scanf("%d",&a[i]); for (i=1;i<=n;++i) { scanf("%d",&b[i]); key[i]=1; } sort(b+1,b+n+1); for (i=1;i<=n;++i) { c[i]=a[i]+b[1];heap[i]=i;pos[i]=i;up(i); } for (i=1;i<=n;++i) { printf("%d ",c[heap[1]]); ++key[pos[1]];c[heap[1]]=a[pos[1]]+b[key[pos[1]]]; down(1); } printf("\n"); }
cogs468noi2010超级钢琴
题目大意:给定n个音符,每个音符有个权值,每次可以选连续的长度为l~r的音符作为和弦,权值为所有选中音符的权值和,求取k个不同和弦的最大权值和。
思路:对于每个左端点i,在堆里放一个决策为[i,l,r],表示起点为i,终点在l~r之间的一个决策,保存下最大值和最大值的位置maxi。每次取出大根堆的根后,把这个区间裂解成两个小区间,决策分别为[i,l,maxi-1][i,maxi+1,r](这里的左端点一定不能搞错,是取出决策中的左端点)。每次放入的时候可以用线段树求出值,用优先队列代替手写堆就可以了。
这种区间裂解使后来的决策不会影响之前的做法需要掌握。这道题目应该也可以用主席树,查询区间第k大,不过显然代码复杂度要高许多。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> #define maxnode 500005 #define inf 50000000000LL using namespace std; struct use{ long long maxn; int maxi; void init(){maxn=-inf;maxi=0;} }tree[maxnode*4]={0}; struct heap{ long long maxn; int li,lr,rr,maxi; }; bool operator<(heap x,heap y){return x.maxn<y.maxn;} priority_queue<heap> que; long long ai[maxnode]={0}; void updata(int i) { if (tree[i*2].maxn>tree[i*2+1].maxn) { tree[i].maxn=tree[i*2].maxn;tree[i].maxi=tree[i*2].maxi; } else { tree[i].maxn=tree[i*2+1].maxn;tree[i].maxi=tree[i*2+1].maxi; } } void build(int i,int l,int r) { int mid; if (l==r) { tree[i].maxn=ai[l];tree[i].maxi=l;return; } mid=(l+r)/2; build(i*2,l,mid);build(i*2+1,mid+1,r); updata(i); } use task(int i,int l,int r,int ll,int rr) { use x1,x2;int mid; x1.init();x2.init(); if (ll<=l&&r<=rr) return tree[i]; mid=(l+r)/2; if (ll<=mid) x1=task(i*2,l,mid,ll,rr); if (rr>mid) x2=task(i*2+1,mid+1,r,ll,rr); if (x2.maxn>x1.maxn){x1.maxn=x2.maxn;x1.maxi=x2.maxi;} return x1; } int main() { int i,j,n,m,k,l,r; long long ans=0; use xi;heap x2; scanf("%d%d%d%d",&n,&k,&l,&r); for (i=1;i<=n;++i) { scanf("%I64d",&ai[i]);ai[i]+=ai[i-1]; } build(1,1,n); for (i=1;i<=n-l+1;++i) { xi=task(1,1,n,i+l-1,min(n,i+r-1)); que.push(x2=(heap){xi.maxn-ai[i-1],i,i+l-1,min(n,i+r-1),xi.maxi}); } for (i=1;i<=k;++i) { x2=que.top();que.pop();ans+=x2.maxn; if (x2.maxi-1>=x2.lr) { xi=task(1,1,n,x2.lr,x2.maxi-1); que.push((heap){xi.maxn-ai[x2.li-1],x2.li,x2.lr,x2.maxi-1,xi.maxi}); } if (x2.maxi+1<=x2.rr) { xi=task(1,1,n,x2.maxi+1,x2.rr); que.push((heap){xi.maxn-ai[x2.li-1],x2.li,x2.maxi+1,x2.rr,xi.maxi}); } } printf("%I64d\n",ans); }