找回密码
 立即注册
查看: 25|回复: 0

2023年蓝桥杯省赛B组整数删除(洛谷P12085):优先队列+双向链表解法

[复制链接]
  • 打卡等级:即来则安
  • 打卡总天数:28
  • 打卡月天数:2
  • 打卡总奖励:194
  • 最近打卡:2025-08-15 15:43:34

36

主题

2

回帖

2544

积分

VIP会员

积分
2544
发表于 2025-7-18 16:42:18 | 显示全部楼层 |阅读模式
一、问题重述
给定N个整数的序列,进行K次操作:每次删除当前最小元素,并将其值加到相邻元素上。要求高效计算出最终序列。
二、完整代码解析(含详细注释)
  1. #include <iostream>
  2. #include <vector>
  3. #include <queue>
  4. #include <set>
  5. using namespace std;

  6. // 定义链表节点结构
  7. struct Node {
  8.     long long value;   // 存储节点值
  9.     int prev, next;    // 前驱和后继指针
  10.     // 重载运算符用于set排序
  11.     bool operator<(const Node& other) const {
  12.         return value < other.value || (value == other.value && prev < other.prev);
  13.     }
  14. };

  15. int main() {
  16.     ios::sync_with_stdio(false);  // 加速输入输出
  17.     cin.tie(nullptr);
  18.    
  19.     int N, K;
  20.     cin >> N >> K;  // 输入数字个数和操作次数
  21.    
  22.     // 初始化双向链表(含哨兵节点)
  23.     vector<Node> nodes(N+2);  
  24.     set<pair<long long, int>> min_heap;  // 使用set模拟最小堆
  25.    
  26.     // 构建初始链表和优先队列
  27.     for (int i = 1; i <= N; ++i) {
  28.         cin >> nodes[i].value;
  29.         nodes[i].prev = i-1;
  30.         nodes[i].next = i+1;
  31.         min_heap.insert({nodes[i].value, i});  // 存入值和位置
  32.     }
  33.     // 设置哨兵节点
  34.     nodes[0].next = 1;     // 头哨兵
  35.     nodes[N+1].prev = N;   // 尾哨兵
  36.    
  37.     vector<bool> deleted(N+2, false);  // 标记删除状态
  38.    
  39.     // 执行K次删除操作
  40.     while (K--) {
  41.         auto it = min_heap.begin();  // 获取最小值
  42.         long long val = it->first;
  43.         int pos = it->second;
  44.         min_heap.erase(it);
  45.         
  46.         // 处理前驱节点
  47.         int prev_pos = nodes[pos].prev;
  48.         if (prev_pos != 0) {  // 如果不是头节点
  49.             min_heap.erase({nodes[prev_pos].value, prev_pos});
  50.             nodes[prev_pos].value += val;  // 前驱节点增值
  51.             min_heap.insert({nodes[prev_pos].value, prev_pos});
  52.         }
  53.         
  54.         // 处理后继节点
  55.         int next_pos = nodes[pos].next;
  56.         if (next_pos != N+1) {  // 如果不是尾节点
  57.             min_heap.erase({nodes[next_pos].value, next_pos});
  58.             nodes[next_pos].value += val;  // 后继节点增值
  59.             min_heap.insert({nodes[next_pos].value, next_pos});
  60.         }
  61.         
  62.         // 更新链表指针
  63.         nodes[prev_pos].next = next_pos;
  64.         nodes[next_pos].prev = prev_pos;
  65.         deleted[pos] = true;  // 标记删除
  66.     }
  67.    
  68.     // 输出最终序列
  69.     int current = nodes[0].next;  // 从**个有效节点开始
  70.     while (current != N+1) {
  71.         cout << nodes[current].value << " ";
  72.         current = nodes[current].next;
  73.     }
  74.    
  75.     return 0;
  76. }
复制代码


三、核心算法解析
  • 数据结构设计

    • 双向链表维护元素间关系
    • set模拟最小堆实现高效取最小值
    • 哨兵节点简化边界条件处理

  • 关键操作流程

    • 每次取出最小值后更新相邻节点
    • 动态维护优先队列中的值
    • 通过标记数组避免重复处理

  • 复杂度分析

    • 时间复杂度:O(KlogN)(每次堆操作logN)
    • 空间复杂度:O(N)(存储链表和堆)

四、常见问题解答
Q:为什么用set而不用priority_queue? A:set支持快速查找和删除任意元素,priority_queue只能访问堆顶
Q:如何处理相同最小值的冲突? A:通过自定义比较函数,当值相同时按位置排序
Q:哨兵节点有什么作用? A:统一处理头尾节点的边界情况,避免额外条件判断
来源:编程学习

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表