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

题解

观察题目 要使用数组最小值 我们想到可以用最小堆来存数组 也就是优先队列
删除最小值后 要将最小值加给他的左右数 我们要使用下标 利用双端队列来实现
还有一个最小值赋值给左右数后 数组最小值改变的问题 如下图
AcWing-4961-整数删除-堆-双向链表-AcWing.png
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;
}
posted @ 2024-04-16 17:20  MsEEi  阅读(3)  评论(0)    收藏  举报