洛谷 P1484 种树 贪心+堆+双向链表

本题其实是在 n n n个数中选出至多 k k k个数,且两两不相邻,并使所选数的和最大。

很容易想到动规思路:f[i][j]表示种到第i棵树且种了j棵的最大获利,则 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 2 ] [ j − 1 ] + a [ i ] ) f[i][j]=max(f[i-1][j],f[i-2][j-1]+a[i]) f[i][j]=max(f[i1][j],f[i2][j1]+a[i]),注意边界、初始化即可。

但是,对于本题 n < = 300000 n<=300000 n<=300000的数据规模,动规显然不足以通过本题,需要另想算法。

我们先进行小规模枚举:

k = 1 k=1 k=1时,显然取n个数中取最大的即可(暂不考虑全负的情况)。设最大的数是 a [ i ] a[i] a[i]

k = 2 k=2 k=2时,则有两种可能:1、另取一个与 a [ i ] a[i] a[i]不相邻的 a [ j ] a[j] a[j]。2、取 a [ i − 1 ] a[i-1] a[i1] a [ i + 1 ] a[i+1] a[i+1]

我们可以发现:如果 k = 1 k=1 k=1时最优解为 a [ i ] a[i] a[i],那么我们便可以把 a [ i − 1 ] a[i-1] a[i1] a [ i + 1 ] a[i+1] a[i+1]进行合并,因为它们要么同时被选,要么同时落选(证明不难,请自行解决)。而且,我们还注意到:当选了 a [ i − 1 ] a[i-1] a[i1] a [ i + 1 ] a[i+1] a[i+1]时,获利便增加了 a [ i − 1 ] + a [ i + 1 ] − a [ i ] a[i-1]+a[i+1]-a[i] a[i1]+a[i+1]a[i]。所以当 a [ i ] a[i] a[i]被选时,我们就可以删去 a [ i − 1 ] a[i-1] a[i1] a [ i + 1 ] a[i+1] a[i+1],并把 a [ i ] a[i] a[i]改成 a [ i − 1 ] + a [ i + 1 ] − a [ i ] a[i-1]+a[i+1]-a[i] a[i1]+a[i+1]a[i],重新找最大的。

每次找的都是最大的数,我们便可以使用堆进行操作,直到堆中最大值小于 0 0 0或取出 k k k个数后停止。复杂度 O ( k l o g n ) O(klogn) O(klogn)

怎么删去 a [ i − 1 ] a[i-1] a[i1] a [ i + 1 ] a[i+1] a[i+1]呢?这里我们使用双向链表的方式来记录每一块 b e g i n − 1 begin-1 begin1 e n d + 1 end+1 end+1。再开一个 v i s [ i ] vis[i] vis[i]数组记录它是否被删去,如果被删去的元素出现在堆顶,直接弹出即可。

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long LL;

const int N = 500000 + 3;

struct HN {
	LL v; int a;
	HN(LL v,int a):v(v),a(a){}
	bool operator < (const HN& rhs) const {
		return v < rhs.v;
	}
};
int n,m;
int l[N],r[N],vis[N];
LL ans = 0;
LL a[N];
priority_queue<HN> q;

int main() {
	ans = 0LL;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) {
		scanf("%lld",&a[i]); q.push(HN(a[i],i));
		l[i] = i-1; r[i] = i+1; vis[i] = 0;
	}
	r[0] = 1; l[n+1] = n;
	while(m--) {
		while(vis[q.top().a]) q.pop();
		HN s = q.top(); q.pop();
		if(s.v < 0) break;
		ans += s.v;
		a[s.a] = a[l[s.a]] + a[r[s.a]] - a[s.a];
		vis[l[s.a]] = vis[r[s.a]] = true; a[l[s.a]] = a[r[s.a]] = 0;
		l[s.a] = l[l[s.a]]; r[l[s.a]] = s.a;
		r[s.a] = r[r[s.a]]; l[r[s.a]] = s.a;
		q.push(HN(a[s.a],s.a));
	}
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值