csp信奥赛C++高频考点专项训练之贪心算法 --【反悔贪心】:种树

csp信奥赛C++高频考点专项训练之贪心算法 --【反悔贪心】:种树

题目描述

cyrcyr 今天在种树,他在一条直线上挖了 n n n 个坑。这 n n n 个坑都可以种树,但为了保证每一棵树都有充足的养料,cyrcyr 不会在相邻的两个坑中种树。而且由于 cyrcyr 的树种不够,他至多会种 k k k 棵树。假设 cyrcyr 有某种神能力,能预知自己在某个坑种树的获利会是多少(可能为负),请你帮助他计算出他的最大获利。

输入格式

第一行,两个正整数 n , k n,k n,k。

第二行, n n n 个整数,第 i i i 个数表示在直线上从左往右数第 i i i 个坑种树的获利。

输出格式

输出一个数,表示 cyrcyr 种树的最大获利。

输入输出样例 1
输入 1
复制代码
6 3 
100 1 -1 100 1 -1
输出 1
复制代码
200
说明/提示

对于 20 % 20\% 20% 的数据, n ≤ 20 n\leq 20 n≤20。

对于 50 % 50\% 50% 的数据, n ≤ 6000 n\leq 6000 n≤6000。

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 300000 1 \le n\leq 300000 1≤n≤300000, 1 ≤ k ≤ n 2 1 \le k\leq \dfrac{n}{2} 1≤k≤2n,在一个地方种树获利的绝对值在 10 6 10^6 106 以内。

思路分析

题目大意:有 n 个坑位(编号1到n),每个坑位种树有美观度 a[i]。不能选择相邻的两个坑位(即选了i就不能选i-1和i+1),总共要选 k 个坑位,求最大总美观度。

核心思路

这道题如果直接贪心每次选最大的,可能会遇到问题:选了最大的,它的左右两边就不能选了,但可能"选左边+右边"比"选中间"更优。反悔贪心可以解决这个问题。

  1. 数据结构准备

    • 大根堆存储每个坑位的(美观度, 位置),堆顶是当前可选的最大值。
    • 双向链表维护每个坑位的前驱 l[i] 和后继 r[i],方便删除操作。
    • 增加一个数组 vis 标记某个坑位是否已被删除(不可选)。
  2. 反悔机制设计

    当我们选中一个坑位 x 时,不能简单地把 x 删掉就完事。我们需要给未来的反悔留一个机会。

    • 如果直接选了 x,那么 x 的左右邻居 l[x]r[x] 被标记为不可选。
    • 但将来可能会发现,不选 x,而是选 l[x] + r[x] 更优。
    • 所以,当我们选中 x 时,我们不删除 x,而是修改 x 的值 为:a[l[x]] + a[r[x]] - a[x],然后把修改后的 x 重新入堆。
    • 这个新值代表什么?将来如果选中这个修改后的 x,就相当于放弃了之前选的 x,转而选择它的左右两个(相当于用两个替换一个,净收益是 a[l[x]] + a[r[x]] - a[x])。这就是反悔机制。
  3. 具体步骤

    • 初始化链表:l[i] = i-1, r[i] = i+1
    • 将所有坑位入大根堆。
    • 循环 k 次(每次选一个坑):
      • 取出堆顶 (val, id),如果该位置已被删除,则跳过。
      • 如果当前 val <= 0,继续选只会减少总和,直接结束。
      • 累加答案 ans += val
      • 记录左右邻居 L = l[id], R = r[id]
      • 将左右邻居标记为删除(因为它们被选了,不能再用)。
      • 更新当前坑位的美观度为 a[L] + a[R] - a[id],存入 a[id]
      • 更新链表:l[id] = l[L], r[id] = r[R],同时修改前后节点的链接。
      • 将更新后的 id 重新入堆(提供反悔机会)。
  4. 为什么正确

    通过这种"选一个加两个"的转换,我们实际上是在维护一个可反悔的决策序列。每次选的都是当前最优的"决策块",这个块可以是一个单独坑位,也可以是"左右+中间"的组合。通过堆和链表,我们始终能保证在选了 k 个不相邻坑位的前提下,总美观度最大。

代码实现

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int N = 500010;
ll a[N]; // 美观度
int l[N], r[N]; // 双向链表
bool vis[N]; // 是否已被删除
struct T {
    ll v; // 美观度
    int i; // 位置
    bool operator < (const T& t) const {
        return v < t.v; // 大根堆
    }
};
priority_queue<T> q;

int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
        l[i] = i - 1; // 前驱
        r[i] = i + 1; // 后继
        q.push({a[i], i});
    }
    
    ll ans = 0;
    while (k--) {
        // 取出堆顶有效元素
        while (vis[q.top().i]) q.pop();
        T t = q.top(); q.pop();
        
        // 如果当前最大值<=0,后续只会减少,直接结束
        if (t.v <= 0) break;
        
        ans += t.v;
        int x = t.i;
        int L = l[x], R = r[x];
        
        // 标记左右节点为删除
        vis[L] = vis[R] = 1;
        
        // 更新当前节点的美观度为"反悔值"
        a[x] = a[L] + a[R] - a[x];
        q.push({a[x], x});
        
        // 更新链表:跳过L和R
        l[x] = l[L];
        r[x] = r[R];
        if (l[x] >= 1) r[l[x]] = x;
        if (r[x] <= n) l[r[x]] = x;
    }
    
    printf("%lld\n", ans);
    return 0;
}

功能分析

  • 数据结构

    • 大根堆 q:存放 (美观度, 位置),快速获取当前可选的最大值。
    • 数组 l[], r[]:双向链表,维护坑位的前后关系,支持 O(1) 删除。
    • 数组 vis[]:标记某个坑位是否已被删除(即被选中作为左右邻居),不可再选。
    • 数组 a[]:动态更新,当选中一个坑位后,它的值变为"反悔值",代表将来可以反悔的选择。
  • 核心逻辑

    • 选坑:每次选堆顶最大值的坑位 x。
    • 反悔准备:把 x 的左右邻居 L、R 标记为删除(因为它们被 x 占了)。
    • 反悔值 :把 x 的值更新为 a[L] + a[R] - a[x],并重新入堆。这个值的意思是:如果以后选这个更新后的 x,就相当于"撤销选 x,改选 L 和 R",净收益就是 a[L] + a[R] - a[x]
    • 链表维护:更新链表,让 x 直接跳过 L、R,指向更远的前后节点。
  • 时间复杂度:每个坑位最多入堆、出堆两次(一次原值,一次反悔值),堆操作 O(log n),总复杂度 O(n log n),完美通过。

各种学习资料,助力大家一站式学习和提升!!!

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	cout<<"##########  一站式掌握信奥赛知识!  ##########";
	cout<<"#############  冲刺信奥赛拿奖!  #############";
	cout<<"######  课程购买后永久学习,不受限制!   ######";
	return 0;
}

【秘籍汇总】(完整csp信奥赛C++学习资料):

1、csp/信奥赛C++,完整信奥赛系列课程(永久学习):

https://edu.csdn.net/lecturer/7901 点击跳转

2、CSP信奥赛C++竞赛拿奖视频课:

https://edu.csdn.net/course/detail/40437 点击跳转

https://edu.csdn.net/course/detail/41081 点击跳转

3、csp信奥赛高频考点知识详解及案例实践:

CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转

CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转

信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html 点击跳转

4、csp信奥赛冲刺一等奖有效刷题题解:

CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新): https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转

信奥赛C++提高组csp-s初赛&复赛真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13125089.html 点击跳转

5、GESP C++考级真题题解:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html 点击跳转

· 文末祝福 ·

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	cout<<"跟着王老师一起学习信奥赛C++";
	cout<<"    成就更好的自己!       ";
	cout<<"  csp信奥赛一等奖属于你!   ";
	return 0;
}
相关推荐
南宫萧幕5 小时前
基于 PSO 的 HEV 能量管理策略:从联合仿真建模到排错实战
开发语言·python·算法·matlab·控制
c++之路5 小时前
C++ 高频易错点
java·jvm·c++
wuminyu5 小时前
专家视角看Java多态性的底层基石vtable(虚函数表)构建过程解析
java·linux·c语言·jvm·c++
charlie1145141915 小时前
现代Qt开发教程(新手篇)1.10——进程
开发语言·c++·qt·学习
生物信息与育种5 小时前
全基因组重测序及群体遗传与进化分析技术服务指南
人工智能·深度学习·算法·数据分析·r语言
AI人工智能+电脑小能手6 小时前
【大白话说Java面试题】【Java基础篇】第23题:ConcurrentHashMap的底层原理是什么
java·开发语言·算法·哈希算法·散列表·hash
葳_人生_蕤6 小时前
hot100——回溯和DFS、BFS
算法·深度优先
Eloudy6 小时前
Steane码的稳定子的生成元集计算过程
算法
MegaDataFlowers6 小时前
快速算法验证流水线
算法