堆
手搓堆
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int h[N], ph[N], hp[N], cnt; //h存值, ph存第k个插入的数对应的下标, hp存下标对应第几个插入的数
void heap_swap(int a, int b);
void down(int x);
void up(int x);
int main()
{
int n, m = 0; cin >> n;
while(n--)
{
string s; cin >> s;
if(s == "I") //插入--在堆尾插,cnt要加一,m也要加一,再up一下
{
int num; cin >> num;
cnt++, m++;
h[cnt] = num;
ph[m] = cnt, hp[cnt] = m;
up(cnt);
}
else if(s == "PM") cout << h[1] << endl; //最小值--这是小根堆,所以堆顶就是最小值
else if(s == "DM") //删除最小值,将堆尾与堆顶交换,cnt减一,再down一下
{
heap_swap(1, cnt);
cnt--;
down(1);
}
else if(s == "D") //删除任意值--将任意值与堆尾交换,cnt减一,若此位置的值变大则down,变小则up,两个都写,只会进行一个
{
int num; cin >> num;
int k = ph[num]; //要先将对应的下标存起来,应为交换以后ph[num]就不是k了
heap_swap(k, cnt);
cnt--;
up(k), down(k);
}
else if(s == "C") //修改值--直接赋值,再up和down
{
int num, nu; cin >> num >> nu;
int k = ph[num];
h[k] = nu;
up(k), down(k);
}
}
}
void heap_swap(int a, int b)
{
swap(h[a], h[b]); //交换值
swap(hp[a], hp[b]); //交换下标对应第几个插入的数
swap(ph[hp[a]], ph[hp[b]]); //交换第几个插入的数对应的下标
//并未交换下标,只是交换了下标的指向
}
void down(int x)
{
int u = x;
int l = u * 2, r = u * 2 + 1;
if(l <= cnt && h[l] > h[u]) u = l;
if(r <= cnt && h[r] > h[u]) u = r;
if(u != x)
{
heap_swap(u, x);
down(u);
}
}
void up(int x)
{
int u = x;
while(u / 2 && h[u / 2] > h[u])
{
heap_swap(u / 2, u);
u /= 2;
}
}
stl
#include <iostream>
#include <vector>
#include <set>
using namespace std;
multiset<int> q;
const int N = 1e5 + 10;
int a[N];
int main()
{
int n, m = 0; cin >> n;
while(n--)
{
string s; cin >> s;
if(s == "I")
{
int num; cin >> num;
q.insert(num);
a[++m] = num;
}
else if(s == "PM") cout << *q.begin() << endl;
else if(s == "DM") q.erase(q.begin());
else if(s == "D")
{
int num; cin >> num;
if(q.find(a[num]) != q.end()) q.erase(q.find(a[num]));
}
else
{
int k, x; cin >> k >> x;
if(q.find(a[k]) != q.end()) q.erase(q.find(a[k]));
a[k] = x;
q.insert(x);
}
}
}
>1 【模板】堆
题目描述
给定一个数列,初始为空,请支持下面三种操作:
- 给定一个整数 \(x\),请将 \(x\) 加入到数列中。
- 输出数列中最小的数。
- 删除数列中最小的数(如果有多个数最小,只删除 \(1\) 个)。
输入格式
第一行是一个整数,表示操作的次数 \(n\)。
接下来 \(n\) 行,每行表示一次操作。每行首先有一个整数 \(op\) 表示操作类型。
- 若 \(op = 1\),则后面有一个整数 \(x\),表示要将 \(x\) 加入数列。
- 若 \(op = 2\),则表示要求输出数列中的最小数。
- 若 \(op = 3\),则表示删除数列中的最小数。如果有多个数最小,只删除 \(1\) 个。
输出格式
对于每个操作 \(2\),输出一行一个整数表示答案。
样例
5
1 2
1 5
2
3
2
2
5
提示
对于 \(100\%\) 的数据,保证 \(1 \leq n \leq 10^6\),\(1 \leq x \lt 2^{31}\),\(op \in \{1, 2, 3\}\)。
代码
#include <iostream>
#include <set>
using namespace std;
multiset<int> q;
const int N = 1e6 + 10;
int a[N], cnt;
int main()
{
int n, m = 0; cin >> n;
while(n--)
{
int op; cin >> op;
if(op == 1)
{
int num; cin >> num;
q.insert(num);
m++;
a[m] = num;
}
else if(op == 2) cout << *q.begin() << endl;
else q.erase(q.begin());
}
}
>2 为大猩猩拍照
题目
https://codeforces.com/contest/2000/problem/E
思路
计算移动正方形时每个格子重复了几次--二维数组差分和前缀和
将最大高度的猩猩放在重复次数最多的格子--用大根堆弹出最大值,cnt--,最大值和当前最大高度相乘
代码
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
bool cmp(int a, int b);
int main()
{
int t; cin >> t;
while(t--)
{
int n, m, k; cin >> n >> m >> k;
vector<vector<ll>> a(n + 10, vector<ll>(m + 10));
for(int i = 1; i <= n - k + 1; i++)
{
for(int j = 1; j <= m - k + 1; j++)
{
a[i][j] += 1;
a[i + k][j + k] += 1;
a[i + k][j] -= 1;
a[i][j + k] -= 1;
}
}
multiset<ll> q;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
q.insert(a[i][j]);
}
}
int w; cin >> w;
vector<int> b(w + 10);
for(int i = 1; i <= w; i++) cin >> b[i];
sort(b.begin() + 1, b.end(), cmp);
ll sum = 0;
for(int i = 1; i <= w; i++)
{
sum += b[i] * (*q.rbegin());
q.erase(--q.end());
}
cout << sum << endl;
}
}
bool cmp(int a, int b)
{
return a > b;
}