基础数据结构3.3——堆
题目上添加了超链接,大家点一下题目就会自动跳转到Poj原题界面~~ 冲鸭冲鸭ヾ(◍°∇°◍)ノ゙。
前言:
我觉得想要理解堆,大家需要先学会堆排序,在教学过程中一般是放在树(图)后面的。如果大家理解困难可以先跳过。
堆(heap)是一种特别的完全二叉树,如果完全二叉树中每个节点的值都小于等于其子节点的值,称为最小堆(min heap);反之,如果每个节点的值都大于等于其子节点的值,称为最大堆(max heap)。
C++ STL中的优先队列priority_queue就是堆的实现,对于堆的应用可以直接使用优先队列,需要包含queue头文件。
优先队列应用很广泛,是贪心算法的重要组成部分。例如,在图算法中,使用优先队列解决基于贪心思想的最小生成树等问题。
一般什么时候用到堆?当题目要求我们需要对一组数据多次反复操作(动态改变),并且每次操作(改变)后依旧要求单调有序。
3.3.1 Fence Repair (3253)
题意:将一个长木板进行n-1次切割,切成n个小木板,切木板的花费等于木板的长度。给出最后切割后的长度,求费用最小的切割方案。
小笔记:堆排平均复杂度O(nlogn),题目的主要操作是需要从集合中反复取出最小的两个值,每次都重新排序的话算法复杂度会很高,采用堆就能很好的解决这个问题。
#include <cstdio>
#include <queue>
using namespace std;
int main()
{
int n;
long long a, ans = 0;
priority_queue<long long, vector<long long>, greater<long long> > Q;
scanf("%d", &n);
while (n--)
{
scanf("%lld", &a);
Q.push(a);
}
while (Q.size() > 1)
{
long long x = Q.top();
Q.pop();
long long y = Q.top();
Q.pop();
ans += x + y;
Q.push(x + y);
}
printf("%lld\n", ans);
return 0;
}
3.3.2 Argus (2051)
题意:输入格式Register n p,其中n代表查询序号,p代表周期,每隔p输出一个n,给出1~k间隔,依次打印输出结果,如果同一时间有多个输出,则按n值从小到大输出。
小笔记:这道题坑挺多的,建议大家自己做一下试试。
#include <cstdio>
#include <queue>
using namespace std;
struct Data
{
int n; //查询序号
int p; //周期
int t; //访问时间
Data(int a, int b) : n(a), p(b), t(b) {}
bool operator<(const Data &a) const
{
return (t > a.t) || (t == a.t && n > a.n);
}
};
int main()
{
priority_queue<Data> Q;
char s[10];
while (scanf("%s", s) && s[0] != '#')
{
int n, p;
scanf("%d%d", &n, &p);
Q.push(Data(n, p));
}
int k;
scanf("%d", &k);
while (k--)
{
Data a = Q.top();
Q.pop();
printf("%d\n", a.n);
a.t += a.p;
Q.push(a);
}
return 0;
}
3.3.3 Black Box (1442)
题意:有一个黑盒子处理一些命令,命令有两种
ADD(x):将x放入黑盒子;
GET:从1开始,第i次GET操作是从把黑盒子里面第i小的数字输出。
a[1…m]表示要放入黑盒子的m个数,u表示第i次GET操作时黑盒子里的数字数量。
小笔记:这道题上了难度。堆能保证每加入一个元素,集合里面的元素都保持有序,但是堆只能取堆顶元素,无法取到中间元素。这里就需要将序列分为两个堆,一个最大堆一个最小堆。
#include <cstdio>
#include <queue>
using namespace std;
int main()
{
priority_queue<int> R; //最大堆
priority_queue<int, vector<int>, greater<int>> L; // 最小堆
int m, n;
scanf("%d%d", &m, &n);
int a[30005];
for (int i = 0; i < m; i++)
scanf("%d", &a[i]);
int i = 0;
while (n--)
{
int u;
scanf("%d", &u);
//先将小于u数量的数放入L
while (i < u)
L.push(a[i++]);
//随时调整,保证R中元素数量为i-1
while (!R.empty() && R.top() > L.top())
{
int t = R.top();
R.pop();
R.push(L.top());
L.pop();
L.push(t);
}
printf("%d\n", L.top());
R.push(L.top());
L.pop();
}
return 0;
}
3.3.4 Sequence (2442)
题意:m个序列,每个序列包含n个非负整数,从每个序列中各取一个数加到一起,这样的和有n^m个,要求从小到大输出前n个和。
小笔记:最直观的做法是枚举所有的和之后排序,但如此做必然一发TEL啊。用贪心可以解决这个问题,优化第一步,题目是在nm个和中求前n个,所以在计算过程中维持一个数量为n的集合就可以了,我们只需要不断计算得到一个n的最大堆即可。
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
priority_queue<int> Q;
int m, n;
scanf("%d%d", &m, &n);
int a[105][2005];
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
scanf("%d", &a[i][j]);
sort(a[i], a[i] + n);
for (int j = 0; i && j < n; j++)
for (int k = 0; k < n; k++)
{
if (j)
{
if (Q.top() > a[i][j] + a[0][k])
Q.pop();
else
break;
}
Q.push(a[i][j] + a[0][k]);
}
for (int k = n - 1; i && k >= 0; k--)
{
a[0][k] = Q.top();
Q.pop();
}
}
for (int i = 0; i < n; i++)
printf("%d ", a[0][i]);
printf("\n");
}
return 0;
}
3.3.5 Moo University - Financial Aid (2010)
题意:c头小牛申请补助,每个小牛有分数和需求两个属性,要求按分数选出n头小牛,n为奇数,它们的总需求不超过f,且位于中位数的分数尽可能小。输出符合条件的分数,如果找不到,输出-1。
小笔记:这题好像数据比较弱,二分答案就能AC。实际挺劝退的。
题解:将小牛的信息保存到二元组a中,两个属性分别是分数和需求,将a按分数递减排序,排序后对a的每个位置进行枚举,分别计算前n/2个元素的需求的和lsum与后n/2个元素的需求的和rsum。lsum和rsum要保证值尽量小,求解过程可以通过两个最大堆来实现。
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 100005;
bool cmp(pair<int, int> a, pair<int, int> b)
{
return a.first > b.first;
}
int main()
{
int n, c, f;
scanf("%d%d%d", &n, &c, &f);
n /= 2; //为了方便,将n/2赋给n
pair<int, int> a[N];
for (int i = 0; i < c; i++)
scanf("%d%d", &a[i].first, &a[i].second);
sort(a, a + c, cmp);
//从前往后将a中前n/2个元素d值插入L中并计算lsum值
int lsum[N]; //保存a中某位置前n/2个元素的d的和
int rsum[N]; //保存a中某位置后n/2个元素的d的和
priority_queue<int> R, L;
for (int i = 0; i < n; i++)
{
lsum[n] += a[i].second;
L.push(a[i].second);
}
//对于接下来的元素,和堆顶值进行比较,小于堆顶值则将堆和lsum更新
for (int i = n; i < c - n; i++)
{
lsum[i + 1] = lsum[i];
if (a[i].second < L.top())
{
lsum[i + 1] = lsum[i] + a[i].second - L.top();
L.pop();
L.push(a[i].second);
}
}
//从后往前将a中后n/2个元素d值插入R中并计算rsum值
for (int i = c - 1; i > c - n - 1; i--)
{
rsum[c - n - 1] += a[i].second;
R.push(a[i].second);
}
//对于接下来的元素,和堆顶值进行比较,小于堆顶值则将堆和rsum更新
for (int i = c - n - 1; i > n - 1; i--)
{
rsum[i - 1] = rsum[i];
if (a[i].second < R.top())
{
rsum[i - 1] = rsum[i] + a[i].second - R.top();
R.pop();
R.push(a[i].second);
}
}
//在满足a[i].second+ lsum [i]+ rsum [i]<f条件情况中取最小的i就是题解,如果所有i都不满足条件,则输出-1
int i;
for (i = n; i < c - n && lsum[i] + a[i].second + rsum[i] > f; i++)
;
printf("%d\n", (i == c - n) ? -1 : a[i].first);
return 0;
}

浙公网安备 33010602011771号