单调优先队列
单调优先队列不同于普通二叉堆实现的优先队列,其复杂度为 O ( n log n ) O(n \log n) O(nlogn)预处理, O ( 1 ) O(1) O(1)时间取堆顶, O ( 1 ) O(1) O(1)时间插入。并且数据必须满足每次插入的数据是单调的。
思路
我们先对数据进行排序,准备两个队列 P P P和 Q Q Q,将排序好的数据放入 P P P队列中,然后每次取出都比较一下 P P P和 Q Q Q的队首元素,取最小/最大的那个并出队,插入的时候向队列 Q Q Q插入到队尾部,由于每次插入的数据都是单调的,因此我们得到的是两个单调的队列,相当于归并排序的归并步骤。
例题
如果使用快速排序,那么此题的时间复杂度可以达到 O ( n ) O(n) O(n)。
#include <bits/stdc++.h>
#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)
using namespace std;
typedef long long ll;
queue<ll> P;
queue<ll> Q;
int cnt[100005];
ll POP()
{
if (P.empty())
{
ll val = Q.front();
Q.pop();
return val;
}
else if (Q.empty())
{
ll val = P.front();
P.pop();
return val;
}
else if (P.front() < Q.front())
{
ll val = P.front();
P.pop();
return val;
}
else
{
ll val = Q.front();
Q.pop();
return val;
}
}
ll read()
{
int f = 1;
ll x = 0;
char s = getchar();
while (s < '0' || s > '9')
{
if (s == '-')
f = -1;
s = getchar();
}
while (s >= '0' && s <= '9')
{
x = x * 10 + s - '0';
s = getchar();
}
x *= f;
return x;
}
int main()
{
int n = read();
for (int i = 0; i < n; i++)
{
ll val = read();
cnt[val]++;
}
for (int i = 0; i < 100005; i++)
{
for (int j = 1; j <= cnt[i]; j++)
{
P.push(i);
}
}
ll ans = 0;
for (int i = 0; i < n - 1; i++)
{
ll a = POP();
ll b = POP();
a += b;
ans += a;
Q.push(a);
}
printf("%lld", ans);
return 0;
}
class Solution {
public:
int minStoneSum(vector<int>& piles, int k)
{
// 首先将原数组排序
sort(piles.begin() , piles.end());
// 使用队列来储存被扔掉过的石头
queue<int> q;
// 指向原有石头的末尾,当它前移时代表之前的元素被删除。当然直接pop_back也是可以的。
auto now = piles.rbegin();
// 当前最大的石头和剩余石头总和
int tmp , ans = 0;
for(auto i : piles)
ans += i;
while(k--)
{
// 若还未扔过石头或原有石头的最大值大于被扔过石头的最大值时,需要扔掉原有石头里最大的,也就是now指向的那堆
// 将反向迭代器now前移,表示这对石头被扔掉了
if(q.empty() || (!q.empty() && now != piles.rend() && *now > q.front()))
tmp = *now++;
// 否则将要扔掉的石头应该位于队列的头部,将它出队
else
{
tmp = q.front();
q.pop();
}
// 将取出的石头扔掉一半后加入队列,并在总和里扣除扔掉的部分
q.push(tmp - tmp / 2);
ans -= tmp / 2;
}
return ans;
}
};