思路:用优先队列维护,存入值和下标,再用一个数组cnt累计每个下标增加的和,当弹出最小的值下标为 i 时,如果此时cnt[i]不等于0,说明它实际的值需要加上cnt[i],我们将其增加后再放回优先对列,注意需要清空cnt[i]。如果此时cnt[i]等于0,那我们就成功弹出当前最小元素,这时需要将其前一个元素和后一个元素值增加,我们需要模拟链表去记录每个元素的前后元素是谁,pre[i]表示下标为i的上一个元素是谁,ne[i]表示下标为 i 的下一个元素是谁,直到堆的元素个数只剩n-k时结束循环。不难想象,堆元素的出入次数是线性的。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> PII;
#define pb(s) push_back(s);
#define SZ(s) ((int)s.size());
#define ms(s,x) memset(s, x, sizeof(s))
#define all(s) s.begin(),s.end()
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 500010;
int n, k;
int pre[N], ne[N], st[N];
LL cnt[N];
void solve()
{
cin >> n >> k;
priority_queue<pair<LL, int>, vector<pair<LL, int>>, greater<pair<LL, int>> >q;
for (int i = 1; i <= n; ++i) {
LL v;
cin >> v;
q.push({v, i});
pre[i] = i - 1;
ne[i] = i + 1;
}
int g = n - k;
while (q.size() > g) {
auto p = q.top(); q.pop();
LL v = p.first, ix = p.second;
if (cnt[ix]) {
q.push({v + cnt[ix], ix});
cnt[ix] = 0;
} else {
int l = pre[ix], r = ne[ix];
st[ix] = 1;
cnt[l] += v;
cnt[r] += v;
ne[l] = r;
pre[r] = l;
}
}
std::vector<LL> a(n + 1);
for (int i = 0; i < g; ++i) {
auto p = q.top(); q.pop();
a[p.second] = p.first + cnt[p.second];
}
for (int i = 1; i <= n; ++i) {
if (!st[i]) cout << a[i] << " ";
}
}
int main()
{
ios_base :: sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t = 1;
while (t--)
{
solve();
}
return 0;
}