洛谷
P2085 最小函数值
https://www.luogu.org/problemnew/show/P2085
题目大意:有n个函数,分别为 F 1 , F 2 , . . . , F n F_1,F_2,...,F_n F1,F2,...,Fn。定义Fi(x)= A i A_i Ai* x 2 x^2 x2+ B i B_i Bix+ C i C_i Ci (x∈N)。给定这些 A i 、 B i 和 C i A_i、B_i和C_i Ai、Bi和Ci,请求出所有函数的所有函数值中最小的m个(如有重复的要输出多个)。
由于n,m很大,这里就不能全部举例然后排序了,会超时,也会爆内存。
所以这里就用到了堆的思想了。维护堆里的最小值,在往里面放然后继续维护。
思路是这样的: 首先,我们可以在所有“箭头”指向1的时候,对所有箭头对应的函数值建立小根堆;然后,每次从堆顶取走那个数,并将其所对应的“箭头”指向下一个函数值,然后把这个新的函数值代替那个取走的函数值放在堆顶,并自顶向下维护堆(大家可以证明一下,一直这样操作下去,堆的性质恒成立)。
代码很简洁,可以套用这类的问题:
#include<bits/stdc++.h>
#include<queue>
using namespace std;
int a[100005],b[100005],c[100005],to[100005],i,n,m;
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q;//先按first排,再按second排
int main()
{
scanf("%d %d",&n,&m);
for (i=1;i<=n;i++){
to[i]=1;
scanf("%d %d %d",&a[i],&b[i],&c[i]);
q.push(pair<int,int>(a[i]+b[i]+c[i],i));
}
while (m--){
printf("%d ",q.top().first);
i=q.top().second;q.pop();
q.push(pair<int,int>(a[i]*(++to[i])*(to[i])+b[i]*to[i]+c[i],i));
}
return 0;
}
同类题:
P1631 序列合并
https://www.luogu.org/problemnew/show/P1631
有两个长度都是N的序列A和B,在A和B中各取一个数相加可以得到 N 2 N^2 N2个和,求这 N 2 N^2 N2个和中最小的N个。
先让 a 1 a_1 a1与所有的 b i b_i bi相加,然后每出栈一个则将 b i 与 下 一 个 a i b_i与下一个a_i bi与下一个ai相加,放入优先队列中。
#include<bits/stdc++.h>
#include<queue>
using namespace std;
int a[100005], b[100005], to[100005],i, n;
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q;//先按first排,再按second排
int main()
{
scanf("%d",&n);
for (i=1;i<=n;i++)
scanf("%d", &a[i]);
for (i=1;i<=n;i++){
scanf("%d",&b[i]);to[i]=1;
q.push(pair<int,int>(a[1]+b[i],i));
}
while (n--){
printf("%d ",q.top().first);
i = q.top().second; q.pop();
q.push(pair<int,int>(a[++to[i]]+b[i],i));
}
return 0;
}
P1801 黑匣子_NOI导刊2010提高(06)
https://www.luogu.org/problemnew/show/P1801
最基本的堆问题。
题目大意:
add往队列里加数,每次get则i++,输出第i大的数。
这只需两个堆,一个大顶堆,一个小顶堆,大顶堆,维持i数量个容积即可。然后放入大顶堆中,大于i的放入小顶堆中,每次比较大顶堆与小顶堆的大小。
#include <cstdio>
#include <queue>
#include <iostream>
using namespace std;
int main(){
int a[200001];
priority_queue<int>A;//单调递减,大顶堆
priority_queue<int,vector<int>,greater<int>>B; //单调递增,小顶堆
int n,m,r=1,q;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++){
cin>>q;
for(int j=r;j<=q;j++){
A.push(a[j]);
if(A.size()==i) B.push(A.top()),A.pop(); //超过大小,移除元素
}
r=q+1;
printf("%d\n",B.top()); //输出每次 GET 的答案
A.push(B.top()),B.pop(); //为下一次的 GET 作准备,填满小顶堆的空间
}
return 0;
}
P1484 种树
https://www.luogu.org/problemnew/show/P1484
cyrcyr今天在种树,他在一条直线上挖了n个坑。这n个坑都可以种树,但为了保证每一棵树都有充足的养料,cyrcyr不会在相邻的两个坑中种树。而且由于cyrcyr的树种不够,他至多会种k棵树。假设cyrcyr有某种神能力,能预知自己在某个坑种树的获利会是多少(可能为负),请你帮助他计算出他的最大获利。
题目很容易想到DP
f[ i ][ j ]表示种到第i棵树且种了j棵的最大获利,则f[ i ][ j ]=max(f[ i-1 ][ j ],f[ i-2 ][ j-1 ]+a[ i ])
但很明显n太大了不成立。
所以这里的做法是:
设k为放入的个数,先找到最大值放入堆中,每次找到最大的如果k=1时最优解为a[i],那么我们便可以把a[i-1]和a[i+1]进行合并,因为它们要么同时被选,要么同时落选(证明不难,请自行解决)。而且,我们还注意到:当选了a[i-1]和a[i+1]时,获利便增加了a[i-1]+a[i+1]-a[i]。所以当a[i]被选时,我们就可以删去a[i-1]和a[i+1],并把a[i]改成a[i-1]+a[i+1]-a[i],重新找最大的。
凭感觉会觉得不成立,但是推一下,是成立的。
每次找的都是最大的数,我们便可以使用堆进行操作,直到堆中最大值小于0或取出k个数后停止。复杂度O(klogn)。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e5+5;
struct cs{
ll v;int id;
bool operator<(const cs &a)const {return v<a.v;}
}t;
priority_queue<cs>Q;
ll a[N],ans;
int l[N],r[N];
bool ok[N];
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&t.v);
a[i]=t.v;
t.id=i;
l[i]=i-1;
r[i]=i+1;
Q.push(t);
}
r[0]=1;l[n+1]=n;
while(m--){
while(ok[Q.top().id])Q.pop();
t=Q.top();Q.pop();
if(t.v<0)break;
ans+=t.v;
int x=t.id;
a[x]=a[l[x]]+a[r[x]]-a[x];
t.v=a[x];
ok[l[x]]=ok[r[x]]=1;
l[x]=l[l[x]];r[l[x]]=x;
r[x]=r[r[x]];l[r[x]]=x;
Q.push(t);
}
printf("%lld",ans);
}
做洛谷堆的题目主要是为了做后面的树状数组和线段树的题目,顺便给堆也做做笔记hhh。