真.调查兵团的种树式 100分解法
该不会真又有人点了之前的100分做法吧
做法
本题第一个思路是贪心,然而常规贪心无法解决不相邻的问题,所以就有了50分的dp做法,此时我们想到只要建立一个大根堆并保证每次出堆后,根顶元素互不相邻,然后累加权值就可以了。
但这么做显然有问题,所以可以把上述思路优化成如果我先取了一个较大的点后来发现取相邻两个点比只取它一个更优那就取相邻的那两个点,
so:两种情况
-
取当前第i个点 :
ans+=a[i] -
取第i个点的相邻两点 :
ans+=a[i-1]+a[i+1]-a[i]
//再次强调,这里讨论这种情况的前提一定是第i个点已经被取了
我们如何才能取到上述情况呢,可以先把每个
再再再考虑到可能取相邻的相邻更优所以每次我们将
实现: 可使用链表进行维护
可能讲得相当模糊,大家看代码吧。同大家也可以通过模拟代码,来理解这道题(因为我讲得太烂了)
代码
#include<cstdio>
#include<queue>
using namespace std;
long long n,m;
long long arr[300005];
struct s{
long long num;
long long id;
};
priority_queue <s> q;
bool operator<(s x,s y){
return x.num<y.num;
}
long long l[300005],r[300005];
long long t[300005];
long long ans;
int main(){
scanf("%lld%lld",&n,&m);
for(long long i=1;i<=n;i++){
scanf("%lld",&arr[i]);
l[i]=i-1;
r[i]=i+1;
q.push({arr[i],i});
}
l[n+1]=n;
r[0]=1;
for(long long i=1;i<=m;i++){
while(t[q.top().id]==1){
q.pop();
}
if(q.top().num<=0){
break;
}
long long wei=q.top().num;
long long poi=q.top().id;
q.pop();
arr[poi]=arr[l[poi]]+arr[r[poi]]-arr[poi];//提供反悔选项
q.push({arr[poi],poi});
t[l[poi]]=1;//标记
t[r[poi]]=1;
l[poi]=l[l[poi]];//更新相邻情况,注意相邻的相邻也要更新
r[poi]=r[r[poi]];
r[l[poi]]=poi;
l[r[poi]]=poi;
ans+=wei;
}
printf("%lld",ans);
return 0;
}
讲得很烂,代码也很丑,大家凑合着看吧orz。 分享两个大佬的题解:
思路看的这篇
代码c的这篇,这篇的思路也很好
题外话
看了大佬们的题解后才发现这类题目叫反悔贪心,贪心思路好似和之前的“糖果”有点像。
个人认为本题贪心策略可以学习,链表维护不相邻的情况也可以学习,但放在一起适用性并不高