[BZOJ 1150] [CTSC2007] 数据备份Backup 【贪心 + 链表】

题目链接:BZOJ - 1150

 

题目分析

可以看出,我们选的 k 条边一定是相邻两点之间的线段。我们可以将每条边看成一个点,那么我们就是要在 n-1 个点中选出互不相邻的 k 个,使它们的和最小。

我们使用一种神奇的贪心,开始的时候将所有的点权加入堆中,然后取 k 次,每次取权值最小的点,然后将这个点的点权加入答案中,我们取了这个点之后就不能取与它相邻的两个点了,所以我们要将这个点和相邻两点的权值都从堆中删除。但是我们最终的答案并不一定要取这个点,有可能我们会不取这个点,而是取与它相邻的两个点。所以我们在删除了这个点和与它相邻的两个点之后,要在堆里加入一个新的点:权值是与这个点相邻的两个点的权值和-这个点的权值,如果之后取到这个新的点就相当于撤销取这个点,而是取与它相邻的两个点,这样取到的点数还是加一的。

然后这样处理之后我们就认为这个新建的点取代了之前的3个点的位置,然后与这3个点两边的点相邻,需要用链表维护。

还有特殊的情况,对于两端的点,与它们相邻的点只有1个,我们要在第一个点之前和最后一个点之后分别加上一个权值为 INF 的点,这样进行处理之后新建的点的权值也一定非常大,之后就不会被取到。

 

代码

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <set>

using namespace std;

const int MaxN = 100000 + 5, INF = 999999999;

int n, k, Ans;
int A[MaxN], d[MaxN], Next[MaxN], Prev[MaxN];

struct ES
{
	int x, y;
	ES() {}
	ES(int a, int b) {x = a; y = b;}
	
	bool operator < (const ES &e) const
	{
		if (y == e.y) return x < e.x;
		return y < e.y;
	}
};

set<ES> S;
set<ES> :: iterator It;

int main()
{
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d", &d[i]);
		A[i] = d[i] - d[i - 1];
	}
	for (int i = 2; i <= n; ++i) 
	{
		Next[i] = i + 1;
		Prev[i] = i - 1;
		S.insert(ES(i, A[i]));
	}
	A[1] = A[n + 1] = INF;
	int x;
	for (int i = 1; i <= k; ++i)
	{
		It = S.begin();
		x = (*It).x;
		Ans += A[x];
		S.erase(ES(x, A[x]));
		S.erase(ES(Prev[x], A[Prev[x]]));
		S.erase(ES(Next[x], A[Next[x]]));
		A[x] = A[Prev[x]] + A[Next[x]] - A[x];
		S.insert(ES(x, A[x]));
		if (Prev[Prev[x]]) Next[Prev[Prev[x]]] = x;
		if (Next[Next[x]]) Prev[Next[Next[x]]] = x;
		Prev[x] = Prev[Prev[x]]; 
		Next[x] = Next[Next[x]];
	}
	printf("%d\n", Ans);
	return 0;
}

  

posted @ 2015-07-05 20:10  JoeFan  阅读(507)  评论(0编辑  收藏  举报