NKOJ3102 取数 [堆][链表]

# NKOJ3102 取数 [堆][链表]

题目传送门

题解

一种很巧妙的链表使用方法。

首先考虑一种贪心的做法,把每个数放入大根堆,每次取最大的一个数(跳过与已取的数相邻的数)

但这样的做法可能会有问题:如果最大的数比与它相邻两数的和要小,那么答案就可能不是最优的。比如一个数列里面全是类似于2 4 3的子数列,取2和3就比取4要优。

所以我们需要设计一种改悔的方法,使得选择堆顶元素和选择最优方案不重复。

在每次取出了堆顶元素 x 之后,再往堆里加入一个新元素,编号为 + + t o t t o t 为已经存在过的总的元素个数),权值为 v [ L [ x ] ] + v [ R [ x ] ] v [ x ] 。与新节点相邻的数就是 L [ x ] 左边的数 L [ L [ x ] ] R [ x ] 右边的数 R [ R [ x ] ] 。修改新节点的链接情况和 L [ L [ x ] ] R [ R [ x ] ] 的链接情况后,将节点放入堆中。

这样一来,如果选到了这个新加入的元素,就说明选择相邻两数优于选择中间的数字

关于选择次数的问题:之前在选 x 的时候,选择次数 + 1 ,现在选择了新节点,选择次数 + 1 ,选择次数恰好加了 2 次。选择次数是对得上的。

#include<iostream>
#include<cstdio>
#include<queue>
#define N 400005
using namespace std;
int A[N],L[N],R[N];bool used[N];
struct node{int id,v;};
bool operator<(const node &a,const node &b){
    if(a.v!=b.v)return a.v<b.v;return a.id>b.id;
}
priority_queue<node>q;
int main(){
    int n,m,Ans=0,cnt=1;scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&A[i]);
        L[i]=i-1,R[i]=i+1;
        if(i==1)L[1]=n;if(i==n)R[n]=1;
        q.push(node{i,A[i]});
    }
    if(m>n/2){printf("Error!");return 0;}
    while(cnt<=m){
        int t=q.top().id;q.pop();
        if(used[t])continue;
        used[t]=used[L[t]]=used[R[t]]=true;
        Ans+=A[t],n++,cnt++;
        A[n]=A[L[t]]+A[R[t]]-A[t];
        L[n]=L[L[t]],R[n]=R[R[t]],R[L[L[t]]]=n,L[R[R[t]]]=n;
        q.push(node{n,A[n]});
    }
    printf("%d",Ans);return 0;
}

猜你喜欢

转载自blog.csdn.net/ArliaStark/article/details/81771412