天天看点

洛谷·bzoj·[APIO/CTSC 2007]数据备份

初见安~这里是两个传送门:洛谷P3620 & bzoj P1150

题目描述

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

已知办公楼都位于同一条街上。你决定给这些办公楼配对(两个一组)。每一对办公楼可以通过在这两个建筑物之间铺设网络电缆使得它们可以互相备份。

然而,网络电缆的费用很高。当地电信公司仅能为你提供 K 条网络电缆,这意味着你仅能为 K 对办公楼(或总计 2K 个办公楼)安排备份。任一个办公楼都属于唯一的配对组(换句话说,这 2K 个办公楼一定是相异的)。

此外,电信公司需按网络电缆的长度(公里数)收费。因而,你需要选择这 K对办公楼使得电缆的总长度尽可能短。换句话说,你需要选择这 K 对办公楼,使得每一对办公楼之间的距离之和(总距离)尽可能小。

下面给出一个示例,假定你有 5 个客户,其办公楼都在一条街上,如下图所示。这 5 个办公楼分别位于距离大街起点 1km, 3km, 4km, 6km 和 12km 处。电信公司仅为你提供 K=2 条电缆。

洛谷·bzoj·[APIO/CTSC 2007]数据备份

上例中最好的配对方案是将第 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≤10 000

题解

贪心果然是个博 大 精 深的算法呢。【大雾

题意很简单——用K条边从n个点里面两两连接,并保证每个点最多被一条边连接。让K条边的总长度最短。

可以可到的一个很明显的贪心策略是每次都连接相邻的两个点【如果是题目本身就要求了的话感觉这一点题目没有描述的很清楚……】,这样的话我们一共就只有n-1条边供选择。再来,转化题意为:有n-1个数字,相邻的两个不能同时选择,求选择K个数字并使其之和最小。

好像有一种可以用dp做的感觉……不管了这里讲贪心【大雾】我们考虑:每次肯定是取最小的那个可以选的点。那么最小的那个点左右还有两个点,就要么选左右两个要么只选最小的那个。【左右两个一定要同时选,因为如果只选一个的话还不如选最小值呢,贪心就没意义了。】所以我们干脆把这三个绑在一起算——假设这三个值为

洛谷·bzoj·[APIO/CTSC 2007]数据备份

,那么我们先假设选择

洛谷·bzoj·[APIO/CTSC 2007]数据备份

。如果

洛谷·bzoj·[APIO/CTSC 2007]数据备份

更优怎么办呢?给自己留一个替换的机会咯——把这三个值取出来的同时放进去一个

洛谷·bzoj·[APIO/CTSC 2007]数据备份

,这样的话再次选择就意味着不选

洛谷·bzoj·[APIO/CTSC 2007]数据备份

了而是

洛谷·bzoj·[APIO/CTSC 2007]数据备份

。这样一来我们操作K次取出最小值即可。

维护最小值可以写一个小根堆,对于连续的三个的维护可以写一个双向链表。

提一个小细节——就是如果取出来的是

洛谷·bzoj·[APIO/CTSC 2007]数据备份

或者是

洛谷·bzoj·[APIO/CTSC 2007]数据备份

这种边缘情况呢?很简单,就如上面说到的“两边的值只选一个还不如选中间那个最小的”的情况,把

洛谷·bzoj·[APIO/CTSC 2007]数据备份

洛谷·bzoj·[APIO/CTSC 2007]数据备份

赋值为极大值,保证不会被选出来用于替换的那种即可,因为一定不会有替换的需求。

上代码——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 100005
using namespace std;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}


struct list {
	int left, right, num;
}lst[maxn];
int n, K; 

struct node {
	int point;
	node() {}
	node(int nn) {point = nn;}
	bool operator < (const node &x) const {return lst[x.point].num < lst[point].num;}
};

priority_queue<node> q;
bool vis[maxn];//标记哪些指针的点的最小值已经没用了 
ll ans = 0;

signed main() {
	n = read(), K = read();
	for(int i = 1; i <= n; i++) lst[i].num = read();
	
	for(int i = n; i > 1; i--) lst[i].num -= lst[i - 1].num;//记得要从后往前求中间值 
	for(int i = 2; i <= n; i++) lst[i].left = i - 1, lst[i].right = i + 1;	
	for(int i = 2; i <= n; i++) q.push(node(i));//node只有一个值……便于排序罢了。 
	
	lst[1].right = 2, lst[n + 1].left = n; lst[n + 1].num = lst[1].num = 0x3f3f3f3f;
	//初始化 
	
	for(int i = 1; i <= K; i++) {
		while(vis[q.top().point]) q.pop();//弹出不能用的点 
		int p = q.top().point;
		q.pop();
		ans += lst[p].num;
		
		register int pre = lst[p].left, nxt = lst[p].right;
		vis[pre] = vis[nxt] = true;//标记 
		
		lst[lst[pre].left].right = p;//维护双向链表指针 
		lst[p].left = lst[pre].left;
		lst[lst[nxt].right].left = p;
		lst[p].right = lst[nxt].right;
		
		pre = lst[pre].num, nxt = lst[nxt].num;//这里就偷个懒而已 
		lst[p].num = pre + nxt - lst[p].num;//更新指针p的值,直接把D_{p-1}+D_{p+1}-D_p的值放进p位置 
		q.push(node(p));
	}
	printf("%lld\n", ans);
	return 0;
}
           

整体代码比较容易,但是思维复杂度……很有意思。

迎评:)

——End——