P3620 [APIO/CTSC 2007]数据备份(堆)

题目

题目描述
你在一家 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 n n k k k,其中 n ( 1 ≤ n ≤ 100000 ) n(1≤n≤100 000) n(1n100000)表示办公楼的数目, k ( 1 ≤ k ≤ n / 2 ) k(1≤k≤n/2) k(1kn/2)表示可利用的网络电缆的数目。

接下来的 n 行每行仅包含一个整数 ( 0 ≤ s ≤ 1000000000 ) (0≤s≤1000 000 000) (0s1000000000), 表示每个办公楼到大街起点处的距离。这些整数将按照从小到大的顺序依次出现。

输出格式:
输出文件应当由一个正整数组成,给出将 2 K 2K 2K 个相异的办公楼连成 K K K 对所需的网络电缆的最小总长度。

输入输出样例
输入样例#1:
5 2
1
3
4
6
12
输出样例#1:
4
说明
30 30 30%的输入数据满足 n ≤ 20 n≤20 n20
60 60 60%的输入数据满足 n ≤ 10000 n≤10 000 n10000

题解

  • 我们可以把相邻两个建筑物之间的道路抽象成一个点,那么两个道路之间的距离就是这个点的点权。
  • 那么题意就简化为了从 n − 1 n-1 n1个点选取 k k k的不相邻的点使得所得答案最大。
  • 当我们选取的第 i i i个点时那么第 i − 1 i-1 i1和第 i + 1 i+1 i+1个点都不能选(根据题目要求只能两两配对)
  • 那么我们可以把每个点压入小跟堆中,当我们弹出一个点时,设当前点为 i i i把当前点权值改为 v a l [ i + 1 ] + v a l [ i − 1 ] − v a l [ i ] val[i+1]+ val[i-1]-val[i] val[i+1]+val[i1]val[i]。(画个图理解一下即可)
  • 对于一个点的左右两个点我们可以用链表维护

code

#include <bits/stdc++.h> 
using namespace std; 
const int maxn = 1e5 + 100; 
const int inf = 0x3f3f3f3f; 
typedef long long LL; 

template <typename T> 
inline void read(T &s) {
	s = 0; 
	T w = 1, ch = getchar(); 
	while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); }
	while (isdigit(ch)) { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
	s *= w; 
}

int n, k; 
LL ans; 
bool vis[maxn]; 
struct node {
	LL val; int id; 
	bool operator < (const node& aa) const {
		return val > aa.val; 
	}
}; 
struct number { LL val; int l, r; } p[maxn]; 
priority_queue <node> q; 

inline void extract(int x) {
	p[x].l = p[p[x].l].l; 
	p[x].r = p[p[x].r].r; 
	p[p[x].l].r = x; 
	p[p[x].r].l = x; 
}

int main() {
	int x, y; 
	read(n), read(k); read(x); 
	for (int i = 1; i < n; ++i) {
		read(y); 
		p[i].val = y - x; 
		x = y; 
		p[i].l = i - 1; 
		p[i].r = i + 1; 
		q.push((node) { p[i].val, i }); 
	}
	p[0].val = p[n].val = inf; 
	for (int i = 1; i <= k; ++i) {
		while (vis[q.top().id]) q.pop(); 
		node now = q.top(); 
		q.pop(); 
		ans += now.val; 
		int id = now.id; 
		vis[p[id].l] = true; 
		vis[p[id].r] = true; 
		p[id].val = p[p[id].l].val + p[p[id].r].val - p[id].val; 
		q.push((node) { p[id].val, id }); 
		extract(id); 
	}
	printf("%lld\n", ans); 
	return 0; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值