算法竞赛进阶指南-61.数据备份

题目链接

算法竞赛进阶指南-61.数据备份

你在一家 IT 公司为大型写字楼或办公楼的计算机数据做备份。

然而数据备份的工作是枯燥乏味的,因此你想设计一个系统让不同的办公楼彼此之间互相备份,而你则坐在家中尽享计算机游戏的乐趣。

已知办公楼都位于同一条街上,你决定给这些办公楼配对(两个一组)。

每一对办公楼可以通过在这两个建筑物之间铺设网络电缆使得它们可以互相备份。

然而,网络电缆的费用很高。

当地电信公司仅能为你提供 KK 条网络电缆,这意味着你仅能为 KK 对办公楼(总计 2K2K 个办公楼)安排备份。

任意一个办公楼都属于唯一的配对组(换句话说,这 2K2K 个办公楼一定是相异的)。

此外,电信公司需按网络电缆的长度(公里数)收费。

因而,你需要选择这 KK 对办公楼使得电缆的总长度尽可能短。

换句话说,你需要选择这 KK 对办公楼,使得每一对办公楼之间的距离之和(总距离)尽可能小。

下面给出一个示例,假定你有 55 个客户,其办公楼都在一条街上,如下图所示。

55 个办公楼分别位于距离大街起点 1km,3km,4km,6km1km, 3km, 4km, 6km 和 $ 12km $ 处。

电信公司仅为你提供 K=2K=2 条电缆。

上例中最好的配对方案是将第 11 个和第 22 个办公楼相连,第 33 个和第 44 个办公楼相连。

这样可按要求使用 K=2K=2 条电缆。

11 条电缆的长度是 3km1km=2km3km-1km=2km,第 22 条电缆的长度是 6km4km=2km6km-4km=2km

这种配对方案需要总长 4km4km 的网络电缆,满足距离之和最小的要求。

输入格式

第一行输入整数 nnKK,其中 nn 表示办公楼的数目,KK 表示可利用的网络电缆的数目。

接下来的 nn 行每行仅包含一个整数 ss,表示每个办公楼到大街起点处的距离。

这些整数将按照从小到大的顺序依次出现。

输出格式

输出应由一个正整数组成,给出将 2K2K 个相异的办公楼连成 KK 对所需的网络电缆的最小总长度。

数据范围

2n1000002 \le n \le 100000,
1Kn/21 \le K \le n/2,
0s10000000000 \le s \le 1000000000

输入样例:

5 2 
1
3
4
6
12

输出样例:

4

Method : 双向链表 + 优先队列

题目意思其实也讲得很明白,数轴上给出NN个点,要求选出其中2K2K个点形成KK对,问这KK对点之间的距离的最小总长度。

#include <queue>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
typedef pair<LL, int> PLI;

const int N = 1e5 + 7;
const int SIZE = N << 1; //149493最低通过

struct Node {
    LL val;
    int l, r;
};

Node node[SIZE];
int head, tail, idx;

void init() {
    idx = 2;
    head = 0, tail = 1;
    node[head].r = tail;
    node[tail].l = head;
}

int insert(int pos, int val) {
    node[idx].val = val;
    node[idx].r = node[pos].r;
    node[idx].l = pos;
    node[node[pos].r].l = idx;
    node[pos].r = idx;
    
    return idx ++;
}

void remove(int pos) {
    node[pos].val = -1;
    node[node[pos].l].r = node[pos].r;
    node[node[pos].r].l = node[pos].l;
}

priority_queue<PLI, vector<PLI>, greater<PLI>> pq;
LL d[N];
int n, k;

int main() {
    init();
    node[head].val = node[tail].val = 1e10;
    scanf("%d %d", &n, &k);
    for (int i = 0; i < n; i ++) scanf("%lld", &d[i]);
    for (int i = n - 1; i > 0; i --) {
        d[i] -= d[i - 1];
        pq.push({d[i], insert(node[tail].l, d[i])});
    }
    
    LL res = 0;
    while (k) {
        auto t = pq.top(); pq.pop();
        
        LL v = t.first,  p = t.second;
        if (v != node[p].val) continue;
        
        res += v;
        LL d = node[node[p].l].val + node[node[p].r].val - v;
        pq.push({d, insert(node[p].r, d)});
        
        remove(node[p].l);
        remove(node[p].r);
        remove(p);
        k --;
    }
    
    cout << res << endl;
    
    return 0;
}

复杂度分析

  • 时间复杂度:O(nlogn){O(n\log n)}, 其中logn\log n为维护堆的时间复杂度。

  • 空间复杂度:O(2N){O(2N)}