P3620题解

P3620 数据备份

题意:

数轴上有 \(n\) 个点,第 \(i\) 点与原点的距离为 \(s_i\)。选出 \(k\) 对数(每个数只能选一次),使每对数中两数之间的距离之和最小。求最小距离之和。

分析:

显然,每对数若想距离最小,则必然是相邻的两数。因此,我们用数组 \(a\) 存储两点间的距离。

我们假设 \(a_i\) 为数组 \(a\) 中的最小值。那么我们有两种选法:

1.选择 \(a_i\)

2.同时选择 \(a_{i-1}\)\(a_{i + 1}\)

首先,若只选择 \(a_{i-1}\)\(a_{i+1}\) 中的一个,我们必然可以把这个数替换为 \(a_i\)。因此,选数时必然是上面两种选法。

由于若选了 \(a_i\),则无法选 \(a_{i-1}\)\(a_{i+1}\),因此,我们可以用一个链表存储这 \(n-1\) 个数,每个点权值为 \(a_i\),在选了第 \(i\) 个点之后将这个点的前驱 \(pre_i\) 和后继 \(nxt_i\) 从链表中删除。与此同时,将答案加上 \(a_i\),并将 \(a_i\) 更新为 \(a_{pre_i}+a_{nxt_i}-a_i\)。至于为何要将 \(a_i\) 更新为 \(a_{pre_i}+a_{nxt_i}-a_i\),因为若选择点 \(pre_i\) 和 点\(nxt_i\) 更优,则这个值会在之后操作时被作为最小值选到,由于答案已加上了 \(a_i\),因此选择点 \(pre_i\) 和点 \(nxt_i\) 需要将答案加上 \(a_{pre_i}+a_{nxt_i}-a_i\)即可。

由于我们每次要选取最小值,因此我们将这些数丢进优先队列,并按 \(a\) 数组递增进行存储。

Code:

/*
user:xcj
time:2022.4.11
*/
#include <bits/stdc++.h>
#define int long long
#define N 100010
using namespace std;

int n, m, ans, a[N], pre[N], nxt[N];
bool v[N];

struct xcj{
    bool operator()(int x, int y){return a[x] > a[y];}
};

priority_queue < int, vector < int >, xcj > q;

inline int read(){
    int s = 0, w = 1;
    char ch = getchar();
    for (; ch < '0' || ch > '9'; w *= ch == '-' ? -1 : 1, ch = getchar());
    for (; ch >= '0' && ch <= '9'; s = s * 10 + ch - '0', ch = getchar());
    return s * w;
}

signed main(){
    n = read(), m = read();
    for (int i = 1; i <= n; ++i) a[i] = read();
    for (int i = 1; i < n; ++i) a[i] = a[i + 1] - a[i], pre[i] = i - 1, nxt[i] = i + 1;
    a[0] = a[n] = 2147482597;
    for (int i = 1; i <= n; ++i) q.push(i);
    for (int i = 1; i <= m; ++i){
        int x = q.top();q.pop();
        while (v[x]) x = q.top(), q.pop();
        ans += a[x];
        a[x] = a[pre[x]] + a[nxt[x]] - a[x];
        v[pre[x]] = v[nxt[x]] = 1;
        pre[x] = pre[pre[x]], nxt[x] = nxt[nxt[x]];
        nxt[pre[x]] = pre[nxt[x]] = x;
        q.push(x);
    }
    printf("%lld\n", ans);
    return 0;
}
posted @ 2022-04-12 18:28  leoair  阅读(24)  评论(0)    收藏  举报