BZOJ2151 种树 [贪心+链表]

Description

A城市有一个巨大的圆形广场,为了绿化环境和净化空气,市政府决定沿圆形广场外圈种一圈树。

园林部门得到指令后,初步规划出n个种树的位置,顺时针编号1到n。并且每个位置都有一个美观度Ai,如果在这里种树就可以得到这Ai的美观度。但由于A城市土壤肥力欠佳,两棵树决不能种在相邻的位置(i号位置和i+1号位置叫相邻位置。值得注意的是1号和n号也算相邻位置!)。

最终市政府给园林部门提供了m棵树苗并要求全部种上,请你帮忙设计种树方案使得美观度总和最大。如果无法将m棵树苗全部种上,给出无解信息。

Input

输入的第一行包含两个正整数n、m。第二行n个整数Ai。

Output

输出一个整数,表示最佳植树方案可以得到的美观度。如果无解输出“Error!”,不包含引号。

Sample Input

7 3

1 2 3 4 5 6 7

Sample Output

15

Hint

对于全部数据:m<=n; -1000<=Ai<=1000; n<=200000;

思路

这道题目看上去很容易想到是DP。实际上却是一种神贪心。。。都是套路

每次贪心选取最大值,答案加上这个最大值,最大值左右两边当然不能再选,于是就标记为访问过。最大值不难想到我们可以用堆进行维护。

但是这个贪心显然是不完善的。当最大值左右两边之和大于它本身时,就会产生错误。于是就有一种神奇的操作:将左右两边之和减去最大值,作为新的节点,覆盖掉原来的最大值,并删去左右两个节点。

原因是要么选最大值,要么选它的左右之和,选最大值时答案加上这个最大值,不再去选新的节点,选左右之和时答案还是加上最大值,只不过后面还要加上新节点的值。也就是当我们再次选取的新节点时,也就相当于选取当时最大值的左右之和,而没有选最大值。

这个神奇的操作有删除左右节点的过程,因此我们要用双向链表进行维护。总的来说这个思路比较难想到,但是代码还是比较好打的。

代码

需要注意的细节:
1.链表时环状的,第一个节点与最后一个相邻
2.进行删除节点时要注意前驱和后继都要修改
3.操作的顺序要注意,先更新值再进行删除

#include <bits/stdc++.h>
#define MAXN 200005

int n, m, ans, num[MAXN], pre[MAXN], next[MAXN];
bool vis[MAXN];

struct node
{
	int v, p;
	bool operator < (const node &x) const
	{
		return v<x.v;
	}
}; 
std::priority_queue <node> q;  //STL优先队列,建立大根堆

int main()
{
	scanf("%d%d",&n,&m);
	if(m*2>n) {printf("Error!\n"); return 0;}  //特判无解情况
	for(int i=1; i<=n; i++)
	{
		scanf("%d",&num[i]);
		q.push((node){num[i], i});
		pre[i]=i-1; next[i]=i+1;  //数组模拟链表
	}
	pre[1]=n; next[n]=1; 
	for(int i=1; i<=m; i++)
	{
		while(vis[q.top().p]) q.pop();  //访问过的直接出队
		node top=q.top(); q.pop();
		ans+=top.v;
		vis[next[top.p]]=vis[pre[top.p]]=1;  //将左右节点标记为访问过的
		num[top.p]=num[pre[top.p]]+num[next[top.p]]-num[top.p]; 
		//删除左右节点
		pre[top.p]=pre[pre[top.p]];
		next[top.p]=next[next[top.p]];
		next[pre[top.p]]=top.p;
		pre[next[top.p]]=top.p;
		q.push((node){num[top.p], top.p});
	}
	printf("%d\n",ans);
} 
posted @ 2018-03-04 11:25  CrazyDave  阅读(243)  评论(0编辑  收藏  举报