堆的应用(topk问题、单调性问题)【刷题反思】

1. topk问题

1.1 题目

题目描述:有一个长度为 n 的数组,值为 a[ i ],要找到数组中第 k 小的数。

                  现增加以操作:1 x:1 代表给数组加一个元素 x 

                                           2    :2 代表查询第 k 小的元素,如果没有 k 个数就输出 -1

输入描述:第一行三个整数,n,m,k(1 <= n,m,k <= 2e5)

                 第二行 n 个整数 a[ i ] (0 <= a[i] <= 1e9)

                 接下来 m 行,每行代表一个操作

输出描述:每次查询输出一个第 k 小的数

输入:

5 4 3
1 2 3 4 5
2
1 1
1 3
2

输出:

3
2

1.2 思想

topk问题,要找到第 k 小,无非分两步:

1. 维护一个大小为 k 的大根堆;

2.每次操作的元素,先进堆,然后删除堆顶元素,维持堆的大小为 k ,此时堆顶元素就是第 k 小

1.3 模拟实现

#include<iostream>
#include<queue>
using namespace std;
priority_queue<int> heap;

int main()
{
	int n, m, k; cin >> n >> m >> k;
	for (int i = 1; i <= n; i++)
	{
		int x; cin >> x;
		heap.push(x);
		//维护一个大小为 k 的大根堆
		if (heap.size() > k) heap.pop();
	}

	for (int i = 1; i <= m; i++)
	{
		int op, x; cin >> op;
		if (op == 1)
		{
			//先插入,再删除,维护堆的大小为 k 
			cin >> x;
			heap.push(x);
			if (heap.size() > k)heap.pop();
		}
		else
		{
			//没有 k 个,返回-1
			if (heap.size() < k)cout << -1 << endl;
			//堆顶元素就是第 k 小
			else cout << heap.top() << endl;
		}
	}
	return 0;
}

2. 单调性求前n个数问题

2.1 多个递增函数的前 m 个最小值

2.1.1 题目

题目描述:有 n 个函数,分别为F1,F2,......,Fn,定义 Fi(X)=Ai * x^2+Bi+Ci(x为正整数),求出所有函数的所有函数值中最小的 m 个

输入描述:第一行输入两个正整数 n 和 m

                  之后 n 行每行三个正整数,其中第 i 行的三个数分别为Ai、Bi和Ci

(1 <= n,m <= 10000)

2.1.2 思想

因为二次函数 A 和 B 都大于 0 ,所有在 x 为正整数的区间,所有的函数都是单调递增的,如果 Fi(x) 不在堆顶,那 Fi(x+1)也必然不可能是堆顶元素

因此,可以先把所有函数(x=1)的值放入堆中,取出堆顶元素,再将堆顶元素对应函数的下一个结果放入堆中,再次取出堆顶元素,依次循环......直到取到 m 个

2.1.3 模拟实现

#include<iostream>
#include<queue>
using namespace std;
const int N = 1e4 + 10;
int a[N], b[N],c[N];

struct node
{
	//每个节点的要素:函数值,对应的函数编号,自变量的值
	int f;
	int i;
	int x;
	//构建小根堆
	bool operator<(const node& x)const
	{
		return f > x.f;
	}
};

//计算函数值
int cacl(int i, int y)
{
	return a[i] * y * y + b[i] * y + c[i];
}

priority_queue<node> heap;

int main()
{
	int n, m; cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i] >> b[i] >> c[i];
	}
	//先将所有x=1的函数值放入堆中
	for (int i = 1; i <= n; i++)
	{
		heap.push({ cacl(i,1),i,1 });
	}

	for (int k = 1; k <= m; k++)
	{
		auto u = heap.top(); heap.pop();
		int i = u.i, f = u.f, x = u.x;
		cout << f << " ";
		//将对应的下一个函数值放入堆中
		heap.push({ cacl(i,x + 1),i,x+1 });
	}

	return 0;
}

2.2 多个序列单调递增的前 N 个最小值

2.2.1 题目

题目描述:有两个长度为 N 的单调不降序列 A,B,在 A,B中各取一个数相加可以得到 N^2 个和,求这 N^2 个和中最小的 N 个(1 <= N <= 10^5)

输入描述:第一行一个正整数 N

                  第二行 N 个整数 A1,A2,......,An

                  第三行 N 个整数 B1,B2,......Bn

输入:

3
2 6 6
1 4 8

输出:

3 6 7

2.2.2 思想

因为序列 A 和 B 都是非递减的,如果 a[i] + b[1] 不是堆顶元素,那 a[i] + b[2] 也必然不可能是堆顶元素

因此,可以先把 a[i] +b[1] 的值放入堆中,取出堆顶元素,再放入下一个元素;即每次拿出堆顶元素 a[i] + b[j],再放入 a[i] + b[ j+1],依次循环......直到取到 N 个

2.2.3 模拟实现

#include<iostream>
#include<queue>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];

struct node
{
	//每个节点放入总值,数组a的下标,数组b的下标
	//方便依次删除放入
	int total;
	int i, j;
	//构建小根堆
	bool operator < (const node& x)const
	{
		return total > x.total;
	}
};

priority_queue<node> heap;

int main()
{
	int n; cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) cin >> b[i];
	//依次放入 a[i]+b[1]
	for (int i = 1; i <= n; i++)
	{
		heap.push({ a[i] + b[1] , i, 1 });
	}
	for (int k = 1; k <= n; k++)
	{
		auto u = heap.top(); heap.pop();
		int total = u.total, i = u.i, j = u.j;
		cout << total << " ";
		//放入 a[i]+b[j+1]
		heap.push({ a[i] + b[j + 1] , i, j + 1 });
	}

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值