洛谷P1484种树——贪心、大根堆、双向链表

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

分析:

一、采用贪心算法。

以下面样例说明:

6

1 6 10 9 -1 4

第一步:所有权值push到优先队列,弹出最大值10,它的id是3,第一棵树种在3号位,记ans=10。

第二步:但观察发现两棵树时应该分别种在2,4位,这里存在“后悔”——第一棵树的位置不对。我们在完成第一步后把3号位左右两位合并成一棵树,权值为6+9-10 (为什么要-10,是因为ans已经赋值为10,需要抵消),将新的权值5 push入优先队列,(别担心,我们把6与9对应的第2,4位做上标记,等会在优先队列里碰到有标记的直接弹出,从而实际上)此时队首权值是5,弹出,记ans=10+5,从而实际两棵树种在2、4位。对于另一种样例:

6

1 6 10 9 -1 8

第一步中ans=10,第二步中因为新的队首是8,则记ans+=8,从而ans=18。两棵树种在3、6位。

二、因为三个位置的权值合并成一个新的位置,所以需要引入双向链表。

三、需要注意的是,这三个位置的左端与右端,仍保留在优先队列里,但弹出时不能加到ans里,所以引入标记变量flg,如果标为1,则直接弹出。

四、关于《优先队列+结构体内重载比较函数》,请参见我写的博客https://blog.csdn.net/qq_36314344/article/details/93629494。

【手工模拟说明】

以此例说明:

6 3

1 6 10 9 -4 8

step1:种一棵树时,当前最优选10,得ans=10,然后把它前后染色,得:

1 6(s) 10 9 (s) -4 8    (用到大根堆,碰到有标记s的直接弹出)

再把“后悔树”种进去,相当于变成:

1 5 -4 8

step2:种第二棵时,当前最优选8,得ans=10+8,然后把它前后染色,再把“后悔树”种进去,相当于变成:

1 5 -12

step3:种第三棵树时,当前最优选5,得ans=10+8+5,然后把它前后染色,再把“后悔树”种进去,相当于变成:

-16

从上面模拟过程中看出,必须引入大根堆、双向链表。

贪心是当前最优,导致最终全局最优。例子中种第1、2、3棵树,很像DP中的阶段。

AC代码:

#include
#include
#include
#define ll long long
using namespace std;
const int MaxN=5e5+5;
int n,m,l[MaxN],r[MaxN];
ll a[MaxN],ans;
bool flg[MaxN];
struct node{
	ll v;
	int id;
};
node t;
bool operator < (const node &a,const node &b){
	return a.v q;
int main(){
	cin>>n>>m;
	l[n+1]=n;
	r[0]=1;
	for(int i=1;i<=n;i++){
		scanf("%lld",&t.v);
		a[i]=t.v;
		t.id=i;
		l[i]=i-1;//左链 
		r[i]=i+1;//右链
		q.push(t);
	}
	while(m--){
		while( flg[ q.top().id] )q.pop();
		t=q.top();
		q.pop();
		if(t.v<=0)break;
		ans+=t.v;
		int x=t.id;
		flg[ l[x] ]=flg[ r[x] ]=1;
		//x左与右的位子做上标记后,如果队列碰到则直接弹出 
		a[x]=a[l[x]]+a[r[x]]-a[x];
		t.v=a[x];
		l[x]=l[ l[x] ];r[ l[x] ]=x;//修改链表 
		r[x]=r[ r[x] ];l[ r[x] ]=x;
		q.push(t);
	}
	printf("%lld",ans);
	return 0;
}

 

你可能感兴趣的:(洛谷P1484种树——贪心、大根堆、双向链表)