AcWing 4961. 整数删除---双向链表 堆
题目
给定一个长度为 \(N\) 的整数数列:\(A_1, A_2, . . . , A_N\)。
你要重复以下操作 \(K\) 次:
每次选择数列中最小的整数(如果最小值不止一个,选择最靠前的),将其删除,并把与它相邻的整数加上被删除的数值。
输出 \(K\) 次操作后的序列。
输入格式
第一行包含两个整数 \(N\) 和 \(K\)。
第二行包含 \(N\) 个整数,\(A_1, A_2, A_3, . . . , A_N\)。
输出格式
输出 \(N − K\) 个整数,中间用一个空格隔开,代表 \(K\) 次操作后的序列。
数据范围
对于 \(20\%\) 的数据,\(1 ≤ K < N ≤ 10000\)。
对于 \(100\%\) 的数据,\(1 ≤ K < N ≤ 5 × 10^5\),\(0 ≤ A_i ≤ 10^8\)。
输入样例:
5 3
1 4 2 8 7
输出样例:
17 7
样例解释
数列变化如下,中括号里的数是当次操作中被选择的数:
[1] 4 2 8 7
5 [2] 8 7
[7] 10 7
17 7
题解
观察题目 要使用数组最小值 我们想到可以用最小堆来存数组 也就是优先队列
删除最小值后 要将最小值加给他的左右数 我们要使用下标 利用双端队列来实现
还有一个最小值赋值给左右数后 数组最小值改变的问题 如下图
https://www.acwing.com/solution/content/186885/
注意! 由于我们会不断加上最小值 那么\(0 <= A{i} <= 10^8\) 一个数的范围就到\(10^8\) 不断加很有可能爆int
我们要开成 long long
记住是所有要用到数据的地方都要开成long long
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
typedef long long LL;
typedef pair<long long, int> PII;
LL l[N], v[N], r[N]; //l[x]存数x的左边数的坐标 v[x]存x的值 r[x]存数x的右边数的坐标
int n, m;
void del(int x) //利用双端队列实现删除最小值第x个数后 将其左右的数连起来 再将其分别加上第x个数的值
{
//将左右数连起来 相当于删除x
l[r[x]] = l[x]; //x右边数的左边变成x的左边数
r[l[x]] = r[x];
//将左右数加上x的值
v[l[x]] += v[x];
v[r[x]] += v[x];
}
int main()
{
//运行时间太长了不好看 关个流
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
priority_queue<PII, vector<PII>, greater<PII>> h; //定义小根堆 要存一个数的值 和 其下标(编号)
r[0] = 1, l[n + 1] = n; //定义双端队列的左右边界 坐标从1到n
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
{
scanf("%d", &v[i]);
l[i] = i - 1;
r[i] = i + 1;
h.push({v[i], i}); //将其值和坐标放入堆 以值的大小存储
}
while (m -- ) //开始取出最小值 共取k次
{
auto t = h.top();
h.pop();
if (t.first != v[t.second]) h.push({v[t.second], t.second}), m ++ ; //如果第i个数的值v[i]在del()中被改变了(也就是被加上被删除的最小值了)之后,此时他不再是当前队列的最小值了,
//但由于heap内的v[i]并未被改变 还是初始值,那么我们就要将i以及其当前的值v[i]重新入堆,在堆中重新排序,当前不能算一次操作次数m++
else del(t.second); //当确实是目前最小值 执行del()
}
//利用我们模拟的双端队列 输出操作完毕后的数组
int head = r[0];
while (head != n + 1)
{
printf("%lld ", v[head]);
head = r[head];
}
return 0;
}