[DBSDFZOJ 多校联训] 就

时间:2023-03-09 18:39:01
[DBSDFZOJ 多校联训] 就

背景描述

一排 N 个数, 第 i 个数是 Ai , 你要找出 K 个不相邻的数, 使得他们的和最大。

请求出这个最大和。

输入格式

第一行两个整数 N 和 K。

接下来一行 N 个整数, 第 i 个整数表示 Ai 。

输出格式

一行一个整数表示最大和, 请注意答案可能会超过 int 范围

样例输入

3 2

4 5 3

样例输出

7

数据范围

对于 20% 的数据, N, K ≤ 20 。

对于 40% 的数据, N, K ≤ 1000 。

对于 60% 的数据, N, K ≤ 10000 。

对于 100% 的数据, N, K ≤ 100000 , 1 ≤ Ai ≤ 1000000000。

第一眼看以为是个思博 $DP$ , 然后发现数据范围 $1 \times 10^{5}$ ...一股子 $O(nlog(n))$ 的味道...

一脸懵逼.png

然后就开始各种瞎**优化然后转背包然后就变成了 $60\%$ 的 $O(n^2)$ 算法.

正解果然特么是个贪心.

首先把所有的点都怼进一棵平衡树, 以权值降序第一关键字, 下标升序为第二关键字排序. 然后建立一个链表保存左侧第一个存在结点与右侧第一个存在结点的下标.

然后每次取值最大的元素, 将它和它左右的结点的数据从平衡树中删除, 再把对应的数据改成它的左侧结点与右侧结点的和减去它本身(为了保留不再选取该结点而改选两侧结点的可能性与结果的正确性). 然后修改联保中保存的左侧第二个与右侧第二个元素的右侧/左侧结点下标.

一直贪心 $k$ 次即可得解.总时间复杂度约为 $O(nlog(n))$

参考代码:

GitHub

 #include <set>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm> #define int long long
const int MAXN=;
const long long INF=0x3FFFFFFFFFFFFFFF; struct Node{
long long value;
int pos;
Node(long long value=,int pos=){
this->value=value;
this->pos=pos;
}
bool friend operator <(const Node &tmp,const Node &y){
return tmp.value!=y.value?tmp.value>y.value:tmp.pos<y.pos;
}
}; int n;
int k;
long long ans;
int next[MAXN];
int front[MAXN];
std::set<Node> s;
long long data[MAXN]; void Initialize();
void Delete(int); signed main(){
Initialize();
while(k--){
int tmp=s.begin()->pos;
ans+=data[tmp];
data[tmp]=data[front[tmp]]+data[next[tmp]]-data[tmp];
s.erase(s.begin());
s.erase(Node(data[front[tmp]],front[tmp]));
s.erase(Node(data[next[tmp]],next[tmp]));
s.insert(Node(data[tmp],tmp));
Delete(tmp);
}
printf("%lld\n",ans);
return ;
} void Delete(int tmp){
if(front[front[tmp]]){
next[front[front[tmp]]]=tmp;
}
if(next[next[tmp]]){
front[next[next[tmp]]]=tmp;
}
front[tmp]=front[front[tmp]];
next[tmp]=next[next[tmp]];
} void Initialize(){
scanf("%lld%lld",&n,&k);
for(int i=;i<=n;i++){
scanf("%lld",data+i);
front[i]=i-;
next[i]=i+;
s.insert(Node(data[i],i));
}
data[]=-INF;
next[n]=;
}

Backup

以及日常图包

[DBSDFZOJ 多校联训] 就