国家集训队 种树 题解

题目传送门

题目大意: 有一个圆环状的广场,不能在相邻的位置种树,每个位置种树有一个美观度,现在要求美观度最大。

题解

考虑贪心。

每次取美观度最大的位置即可,但是要考虑一种需要反悔的情况——取 x x x 旁边的两个位置比取 x x x 更优。那么只需要在取了 x x x 之后,往堆里面加一个 v a l [ p r e ] + v a l [ n e x t ] − v a l [ x ] val[pre]+val[next]-val[x] val[pre]+val[next]val[x] p r e pre pre n e x t next next 是指前驱和后继。同时,还需要将堆里面的 v a l [ p r e ] val[pre] val[pre] v a l [ n e x t ] val[next] val[next] 删掉,相当于把 p r e , x , n e x t pre,x,next pre,x,next 三个点合并成一个点。

代码如下(手写堆有点丑……):

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 200010

int n,m;
struct node{int x,y;};
struct dd{
	node a[maxn];
	int t;
	dd():t(0){}
	void up(int x)
	{
		while(x>1&&a[x].x>a[x/2].x)
		{
			swap(a[x],a[x/2]);
			x/=2;
		}
	}
	void add(int x,int y)
	{
		a[++t]=(node){x,y};
		up(t);
	}
	void down(int x)
	{
		if(x>t/2)return;
		int ans=x;
		if(a[x*2].x>a[x].x)ans=x*2;
		if(x*2+1<=t&&a[x*2+1].x>a[ans].x)ans=x*2+1;
		if(x!=ans)
		{
			swap(a[x],a[ans]);
			down(ans);
		}
	}
	node pop()
	{
		node re=a[1];
		a[1]=a[t--];
		down(1);
		return re;
	}
	bool empty(){return t==0;}
}dui;
int a[maxn],next[maxn],pre[maxn];
bool del[maxn];

int main()
{
	scanf("%d %d",&n,&m);
	if(m>n/2)return printf("Error!"),0;
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]),dui.add(a[i],i),next[i]=i+1,pre[i]=i-1;
	pre[1]=n;next[n]=1;//这是个环
	int ans=0;
	while(m--)
	{
		while(del[dui.a[1].y])dui.pop();
		node now=dui.pop();
		ans+=now.x;
		a[now.y]=a[next[now.y]]+a[pre[now.y]]-a[now.y];
		del[next[now.y]]=del[pre[now.y]]=true;
		next[now.y]=next[next[now.y]];pre[now.y]=pre[pre[now.y]];//更新前驱后继
		pre[next[now.y]]=next[pre[now.y]]=now.y;
		dui.add(a[now.y],now.y);
	}
	printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值