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;
}