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;
}
浙公网安备 33010602011771号