问题描述
你在一家 IT 公司为大型写字楼或办公楼(offices)的计算机数据做备份。然而数据备份的工作是枯燥乏味的,因此你想设计一个系统让不同的办公楼彼此之间互相备份,而你则坐在家中尽享计算机游戏的乐趣。
已知办公楼都位于同一条街上。你决定给这些办公楼配对(两个一组)。每一对办公楼可以通过在这两个建筑物之间铺设网络电缆使得它们可以互相备份。
然而,网络电缆的费用很高。当地电信公司仅能为你提供 K 条网络电缆,这意味着你仅能为 K 对办公楼(或总计 2K 个办公楼)安排备份。任一个办公楼都属于唯一的配对组(换句话说,这 2K 个办公楼一定是相异的)。
此外,电信公司需按网络电缆的长度(公里数)收费。因而,你需要选择这 K对办公楼使得电缆的总长度尽可能短。换句话说,你需要选择这 K 对办公楼,使得每一对办公楼之间的距离之和(总距离)尽可能小。
下面给出一个示例,假定你有 5 个客户,其办公楼都在一条街上,如下图所示。这 5 个办公楼分别位于距离大街起点 1km, 3km, 4km, 6km 和 12km 处。电信公司仅为你提供 K=2 条电缆。
上例中最好的配对方案是将第 1 个和第 2 个办公楼相连,第 3 个和第 4 个办公楼相连。这样可按要求使用 K=2 条电缆。第 1 条电缆的长度是 3km―1km = 2km,第 2 条电缆的长度是 6km―4km = 2 km。这种配对方案需要总长 4km 的网络电缆,满足距离之和最小的要求。
输入格式
输入文件的第一行包含整数 n 和 k,其中 n(1≤n≤100 000)表示办公楼的数目,k(1≤k≤n/2)表示可利用的网络电缆的数目。
接下来的 n 行每行仅包含一个整数(0≤s≤1000 000 000), 表示每个办公楼到大街起点处的距离。这些整数将按照从小到大的顺序依次出现。
输出格式
输出文件应当由一个正整数组成,给出将 2K 个相异的办公楼连成 K 对所需的网络电缆的最小总长度。
样例输入
5 2
1
3
4
6
12
样例输出
4
数据范围
30%的输入数据满足 n≤20。
60%的输入数据满足 n≤10000
解析
先考虑贪心。一个显然的结论是不可能跨过一些建筑物连线,这样不可能更优。那么有这么一个贪心策略:每次选择相邻点中最短的线路,然后把这条路线两边的点标记为不可选。当然这是不对的,可能不选两边带来的损失更大。有什么方法能消除后效性?
这里采用加入反悔决策的方法。设删去的边长度为\(l_i\),那么两边的长度分别为\(l_{i-1}\)、\(l_{i+1}\)。当我们选择\(l_i\)时,删去\(l_{i-1}\)、\(l_{i+1}\)的同时加入\(l_{i-1}+l_{i+1}-l_i\)的选择表示反悔。这样,就可以保证决策正确了。
由于涉及到相邻点的改变,我们可以用链表加堆来实现这个过程。
代码
#include <iostream>
#include <cstdio>
#include <queue>
#define int long long
#define N 200002
using namespace std;
priority_queue<pair<int,int> > q;
int n,k,i,pre[N],nxt[N],d[N],a[N],ans,cnt;
bool vis[N];
int read()
{
char c=getchar();
int w=0;
while(c<'0'||c>'9') c=getchar();
while(c<='9'&&c>='0'){
w=w*10+c-'0';
c=getchar();
}
return w;
}
signed main()
{
n=read();k=read();
for(i=0;i<n;i++){
a[i]=read();
if(i>=1){
d[i]=a[i]-a[i-1];
q.push(make_pair(-d[i],i));
pre[i]=i-1;nxt[i]=i+1;
}
}
d[0]=d[n]=1<<30;
for(i=1;i<=k;i++){
while(!q.empty()){
int x=q.top().second,dis=-q.top().first;
q.pop();
if(vis[x]) continue;
ans+=dis;
vis[pre[x]]=vis[nxt[x]]=1;
d[x]=d[pre[x]]+d[nxt[x]]-d[x];
q.push(make_pair(-d[x],x));
nxt[pre[pre[x]]]=x;
pre[nxt[nxt[x]]]=x;
pre[x]=pre[pre[x]];
nxt[x]=nxt[nxt[x]];
break;
}
}
printf("%lld\n",ans);
return 0;
}