本题其实是在nnn个数中选出至多kkk个数,且两两不相邻,并使所选数的和最大。
很容易想到动规思路:f[i][j]表示种到第i棵树且种了j棵的最大获利,则f[i][j]=max(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[i−1][j],f[i−2][j−1]+a[i]),注意边界、初始化即可。
但是,对于本题n<=300000n<=300000n<=300000的数据规模,动规显然不足以通过本题,需要另想算法。
我们先进行小规模枚举:
k=1k=1k=1时,显然取n个数中取最大的即可(暂不考虑全负的情况)。设最大的数是a[i]a[i]a[i]。
k=2k=2k=2时,则有两种可能:1、另取一个与a[i]a[i]a[i]不相邻的a[j]a[j]a[j]。2、取a[i−1]a[i-1]a[i−1] 和 a[i+1]a[i+1]a[i+1]。
我们可以发现:如果k=1k=1k=1时最优解为a[i]a[i]a[i],那么我们便可以把a[i−1]a[i-1]a[i−1]和a[i+1]a[i+1]a[i+1]进行合并,因为它们要么同时被选,要么同时落选(证明不难,请自行解决)。而且,我们还注意到:当选了a[i−1]a[i-1]a[i−1]和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[i−1]+a[i+1]−a[i]。所以当a[i]a[i]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]改成a[i−1]+a[i+1]−a[i]a[i-1]+a[i+1]-a[i]a[i−1]+a[i+1]−a[i],重新找最大的。
每次找的都是最大的数,我们便可以使用堆进行操作,直到堆中最大值小于000或取出kkk个数后停止。复杂度O(klogn)O(klogn)O(klogn)。
怎么删去a[i−1]a[i-1]a[i−1]和a[i+1]a[i+1]a[i+1]呢?这里我们使用双向链表的方式来记录每一块begin−1begin-1begin−1和end+1end+1end+1。再开一个vis[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;
}
本文探讨了一种在给定数值序列中选取至多k个不相邻数以最大化总和的高效算法。通过对最大值的迭代选择与合并,利用优先队列与双向链表优化删除过程,实现O(klogn)的时间复杂度。
9117

被折叠的 条评论
为什么被折叠?



