题目
维护一个集合,初始时集合为空,支持如下几种操作:
- “I x”,插入一个数x;
- “PM”,输出当前集合中的最小值;
- “DM”,删除当前集合中的最小值(数据保证此时的最小值唯一);
- “D k”,删除第k个插入的数;
- “C k x”,修改第k个插入的数,将其变为x;
现在要进行N次操作,对于所有第2个操作,输出当前集合的最小值。
输入格式
第一行包含整数N。
接下来N行,每行包含一个操作指令,操作指令为”I x”,”PM”,”DM”,”D k”或”C k x”中的一种。
输出格式
对于每个输出指令“PM”,输出一个结果,表示当前集合中的最小值。
每个结果占一行。
数据范围
$1≤N≤10^5$
$−10^9≤x≤10^9$
数据保证合法。
输入样例:
8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM
输出样例:
-10
6
分析
目标:可以通过插入顺序来操作堆中的元素。
困难:本来可以通过下标代表插入顺序,但是由于存在交换操作,下标不再能够代表插入顺序了。
手段:
1. 维护从堆元素下标到该元素插入顺序的映射。
2. 维护从堆元素插入顺序到该元素下标的映射。
曲线救国,如此便可通过插入顺序来访问堆中的元素。
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
// h:堆
// cnt:堆大小
// hp:从堆的下标映射到插入顺序
// ph:从插入顺序映射到堆的下标
int h[N], hp[N], ph[N], cnt;
void heap_swap(int i, int j)
{
swap(h[i], h[j]);
swap(hp[i], hp[j]);
swap(ph[hp[i]], ph[hp[j]]);
}
void down(int i)
{
int t = i;
if (2 * i <= cnt && h[2 * i] < h[t]) t = 2 * i;
if (2 * i + 1 <= cnt && h[2 * i + 1] < h[t]) t = 2 * i + 1;
if (t != i)
{
heap_swap(t, i);
down(t);
}
}
void up(int i)
{
while(i / 2 && h[i / 2] > h[i])
{
heap_swap(i / 2, i);
i = i / 2;
}
}
int main()
{
// seq:当前插入元素的顺序
int n, seq = 0;
cin >> n;
while(n --)
{
string op;
int k, x;
cin >> op;
if (op == "I")
{
cin >> x;
++ seq;
++ cnt;
h[cnt] = x;
hp[cnt] = seq;
ph[seq] = cnt;
up(cnt);
}
else if (op == "PM")
cout << h[1] << endl;
else if (op == "DM")
{
heap_swap(1, cnt);
-- cnt;
down(1);
}
else if (op == "D")
{
cin >> k;
int i = ph[k];
heap_swap(i, cnt);
-- cnt;
up(i), down(i);
}
else
{
cin >> k >> x;
int i = ph[k];
h[i] = x;
up(i), down(i);
}
}
return 0;
}