算法竞赛进阶指南---0x17(二叉堆) 数据备份

题面

在这里插入图片描述

题解

在这里插入图片描述

  1. 假设选一段,肯定是选最小的;那么选两段呢,就有两种情况,第一种是不破坏34节点,再选一段34节点以外的,第二种就是破坏34节点,那么我们就要选23和45了,为什么选连续的两段呢,假如我们只选了23,然后选择67,那么破坏34就没有必要,因为34是最小的,所以只要是破坏34,就一定是选23,45 当然,对于n段也是一样的。

在这里插入图片描述

  1. 那么如何计算破坏一段连续的区间之后的值呢,通过推导可以发现,每次增加的值就是将两边的值 - 这段连续区间的值,这样我们每次连接两个点后,就将它的值更新成左右节点的距离-此节点的距离,如果下次用到,那么一定是破坏这个点后连续的一段。
  1. 这样我们就可以将两种决策合并成一种,每次取出一个最小值,然后将其距离值更新,再放入set中,因为我们要用到一个节点的左右节点,所以用双链表存储节点的关系

代码

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<set>
#include<vector>
#include<queue>

using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
const int N = 1e5 + 10;

ll n, k;
ll a[N];
ll e[N], l[N], r[N];

void remove(ll x) {
    
    
    l[r[x]] = l[x];
    r[l[x]] = r[x];
}

int main() {
    
    

    cin >> n >> k;
    for (int i = 0; i < n; i++) scanf("%lld", &a[i]);
    for (int i = n - 1; i > 0; i--) e[i] = a[i] - a[i - 1];

    set<PLL> st;
    e[0] = 1e12, e[n] = 1e12;
    for (int i = 0; i <= n; i++) {
    
    
        l[i] = i - 1;
        r[i] = i + 1;
        st.insert({
    
    e[i], i});
    }

    ll res = 0;
    while (k--) {
    
    
        auto it = st.begin();
        ll d = it->first;
        ll index = it->second;
        ll left = l[index], right = r[index];
        st.erase(it);
        st.erase({
    
    e[left],left}),st.erase({
    
    e[right],right});
        res += d;
        remove(left), remove(right);

        e[index] = e[left] + e[right] - d;
        st.insert({
    
    e[index], index});
    }
    cout << res << endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44791484/article/details/113860287