优先队列+反悔(不用动态规划)

题目:https://www.luogu.org/problemnew/show/P1484
题解
本题其实是在n个数中选出至多k个数,且两两不相邻,并使所选数的和最大。
很容易想到动规思路:f[i][j]表示种到第i棵树且种了j棵的最大获利,则f[i][j]=max(f[i-1][j],f[i-2][j-1]+a[i]),注意边界、初始化即可。
但是,对于本题n<=300000的数据规模,动规显然不足以通过本题,需要另想算法。
我们先进行小规模枚举:
k=1时,显然取n个数中取最大的即可(暂不考虑全负的情况)。设最大的数是a[i]。
k=2时,则有两种可能:1、另取一个与a[i]不相邻的a[j]。2、取a[i-1]和a[i+1]。
我们可以发现:如果k=1时最优解为a[i],那么我们便可以把a[i-1]和a[i+1]进行合并,因为它们要么同时被选,要么同时落选(证明不难,请自行解决)。而且,我们还注意到:当选了a[i-1]和a[i+1]时,获利便增加了a[i-1]+a[i+1]-a[i]。所以当a[i]被选时,我们就可以删去a[i-1]和a[i+1],并把a[i]改成a[i-1]+a[i+1]-a[i],重新找最大的
每次找的都是最大的数,我们便可以使用堆进行操作,直到堆中最大值小于0或取出k个数后停止。复杂度O(klogn)。
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define N 500007
#define int long long//一劳永逸 
struct Place{int val,l,r;}p[N];
struct Node{//往堆中放的元素 
	int val,id;
	friend bool operator<(Node s1,Node s2)//operator 必须有返回类型 
	{
		return s1.val<s2.val; 
	}//利用重载进行排序,大根堆 
};    
int n,m,ans;
bool vis[N];//标记数组 
priority_queue<Node> q;
void Del(int x){
	p[x].l=p[p[x].l].l;p[x].r=p[p[x].r].r;
	p[p[x].l].r=x;p[p[x].r].l=x;//删除节点 
}
signed main(){//int在上面被替换掉了,这里用无符号数代替 
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>p[i].val;
		p[i].l=i-1;p[i].r=i+1;
		q.push((Node){p[i].val,i});//入队 
	}
	for(int i=1;i<=m;i++){//最多种m棵树 
	    while(vis[q.top().id]) q.pop();//排除优先队列中已经被访问的点
		Node now=q.top();q.pop();
		if(now.val<=0) break;//大根堆中最大的不是正的,那么就没有再种的必要
		ans+=now.val;//计算总收益 
		vis[p[now.id].l]=vis[p[now.id].r]=1;//标记 
		p[now.id].val=p[p[now.id].l].val+p[p[now.id].r].val-p[now.id].val;
		//以上这个是反悔策略的权值公式 
		q.push((Node){p[now.id].val,now.id});
		Del(now.id);//反悔策略的操作 
	}
	cout<<ans;
	return 0;
 } 

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值