luogu P1484 种树

一道好的堆题!!!

题目传送门:https://www.luogu.org/problemnew/show/P1484



题意:

给出n个数,求至多选k个数的总和的最大值,要求所选的数两两不能相邻。



思路:

dp很好打,可是时间上过不去。

于是,我们想到(题解想到)这样一种思路:对于n个数中最大值a[i]>0,假设它两边都有数,我们可以想到,在k=1的时候,结果为a[i];而在k=2时,要么a[i-1]和a[i+1]同时被选(即a[i-1]+a[i+1]>a[i]),要么同时放弃(即a[i-1]+a[i+1]<a[i])才能满足贪心,因为如果只选其一的话,它们中的任意一个都比a[i]小,所以最优情况就是选它们两个或者都不选(它们的和可能小于a[i])。

所以综上我们可以考虑到:可以将现在最大数的两边合并,如果它们的和大于最大数,就舍弃最大数,其结果增加value[i-1]+value[i+1]-value[i]。而当整个数列都被过完或最大值已为负数(结果只会负增长)的时候,就结束了。


代码:

(思路看懂了吧,代码却比较难懂)

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
struct node{int v,id;bool operator<(const node &t)const{return v<t.v;}} p;
priority_queue<node> a;
	int v[600000],l[600000],r[600000];
	bool bz[600000];
	int n,k;
	long long ans=0;
int main()
{
	scanf("%d %d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&v[i]);
		p.v=v[i];//记录当前数的价值 
		p.id=i;//记录当前数的编号 
		l[i]=i-1;//假设v[i]为最大数,记录他左边的数的编号 
		r[i]=i+1;//假设v[i]为最大数,记录他右边的数的编号 
		a.push(p);//将当前数的价值和编号推入堆中 
	}
	r[0]=1;l[n+1]=n;//将位置0的右边设为1,位置n+1的左边设为n(实际本来就是这样) 
	memset(bz,true,sizeof(bz));
	while(k--)
	{
		while(!bz[a.top().id]) a.pop();//如果当前数已被访问,就不再访问 
		p=a.top();//取出当前最大数 
		a.pop(); 
		if(p.v<0) break;//如果最大数已为负,就break 
		ans+=p.v;//结果加上当前数的价值 
		int id=p.id;
		v[id]=v[l[id]]+v[r[id]]-v[id];//合并过程
		p.v=v[id];//价值改为合并后的价值 
		bz[l[id]]=bz[r[id]]=false;//因为已经合并,所以最大数的左右两边变为false,下次不用再访问 
		l[id]=l[l[id]];r[l[id]]=id;//*详见下图 
		r[id]=r[r[id]];l[r[id]]=id;//*详见下图 
		a.push(p);//将合并后的编号(不变)和价值推入堆中 
	}
	printf("%lld",ans);
} 

PS(最难理解部分,其实也不难,如图):

		l[id]=l[l[id]];r[l[id]]=id;//1.合并后,最大值左边变为最大值左边的左边;2.合并后,最大值左边的右边还是最大值
		r[id]=r[r[id]];l[r[id]]=id;//2.合并后,最大值右边变为最大值右边的右边;2.合并后,最大值右边的左边还是最大值


  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值