CSP-J/S高频考点:P1803线段覆盖详解|贪心算法与活动安排问题

一、问题引入(竞赛背景)

题目溯源:洛谷P1803凌乱的yyy/线段覆盖

题目核心:在n个比赛(每个比赛有开始和结束时间)中,选择互不冲突的最大比赛数量。

竞赛价值:贪心算法是CSP-J/S竞赛的重要考点,考查选手的问题建模和算法优化能力,占分约15-20分。

二、问题分析与算法思路

2.1 问题特性分析

关键约束

  • 时间冲突:不能同时参加两个及以上比赛
  • 全程参与:参加比赛必须从开始到结束
  • 最大化数量:目标是参加尽可能多的比赛

问题本质:经典的活动安排问题(Activity Selection Problem)


2.2 贪心算法策略

最优策略:每次选择结束时间最早的可用比赛

正确性证明

  1. 贪心选择性质:结束时间最早的比赛必在某个最优解中
  2. 最优子结构:选择后剩余问题仍是最优子问题

三、代码实现详解

3.1 AC代码

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

struct Contest {
    int start;
    int end;
};

bool compare(const Contest& a, const Contest& b) {
    return a.end < b.end;  // 按结束时间升序排序
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    
    vector<Contest> contests(n);
    for (int i = 0; i < n; i++) {
        cin >> contests[i].start >> contests[i].end;
    }
    
    // 按结束时间排序
    sort(contests.begin(), contests.end(), compare);
    
    int count = 0;      // 已选择比赛数量
    int lastEnd = -1;    // 最后选择的比赛结束时间
    
    for (int i = 0; i < n; i++) {
        // 如果当前比赛开始时间不早于最后选择的比赛结束时间
        if (contests[i].start >= lastEnd) {
            count++;
            lastEnd = contests[i].end;
        }
    }
    
    cout << count << endl;
    return 0;
}

3.2 优化版本(处理大规模数据)

#include <bits/stdc++.h>
using namespace std;

struct Game {
    int a, b;
    
    bool operator<(const Game& other) const {
        return b < other.b;
    }
};

int main() {
    int n;
    scanf("%d", &n);
    
    vector<Game> games(n);
    for (int i = 0; i < n; i++) {
        scanf("%d%d", &games[i].a, &games[i].b);
    }
    
    sort(games.begin(), games.end());
    
    int ans = 0, current_end = -1;
    for (const auto& game : games) {
        if (game.a >= current_end) {
            ans++;
            current_end = game.b;
        }
    }
    
    printf("%d\n", ans);
    return 0;
}

四、算法原理深度解析

4.1 贪心选择正确性证明

数学归纳法

  1. 基础情况:n=1时,显然选择唯一的比赛最优
  2. 归纳假设:假设对于k个比赛,贪心策略最优
  3. 归纳步骤:对于k+1个比赛,选择结束时间最早的比赛e₁
    • 如果最优解包含e₁,则成立
    • 如果最优解不包含e₁,用e₁替换第一个活动,解不会变差

交换论证: 假设存在最优解S不包含结束时间最早的活动e₁。设S中第一个活动为eⱼ(eⱼ.end ≥ e₁.end)。用e₁替换eⱼ,新解S'仍然可行且数量不变。

4.2 时间复杂度分析

主要操作

  • 排序:O(n log n),n ≤ 10⁶完全可接受
  • 贪心选择:O(n)
  • 总复杂度:O(n log n)

五、测试用例验证

5.1 题目样例验证

输入样例

3
0 2
2 4
1 3

计算过程

  1. 排序结果(按结束时间):

    • (0,2) → 结束时间2
    • (1,3) → 结束时间3
    • (2,4) → 结束时间4
  2. 贪心选择

    • 选择(0,2):count=1, lastEnd=2
    • (1,3):开始时间1 < 2,冲突,跳过
    • (2,4):开始时间2 ≥ 2,选择,count=2, lastEnd=4
  3. 最终结果:2 ✓

5.2 边界测试用例

void test_cases() {
    // 测试1:所有比赛不冲突
    test_case(3, {{1,2},{3,4},{5,6}}); // 预期输出3
    
    // 测试2:所有比赛冲突
    test_case(3, {{1,5},{2,4},{3,6}}); // 预期输出1
    
    // 测试3:大规模数据
    test_case(1000000, generate_large_data()); // 测试性能
}

六、避坑指南与调试技巧

6.1 常见错误分析

错误1:排序规则错误

// 错误:按开始时间排序
return a.start < b.start; // 可能导致选择冲突的比赛

// 正确:按结束时间排序
return a.end < b.end;

错误2:初始值设置错误

// 错误:lastEnd初始值为0
int lastEnd = 0; // 可能错过开始时间为0的比赛

// 正确:初始值为-1
int lastEnd = -1; // 确保第一个比赛能被选择

错误3:比较条件错误

// 错误:使用大于号
if (contests[i].start > lastEnd) // 可能错过开始时间等于结束时间的比赛

// 正确:使用大于等于
if (contests[i].start >= lastEnd)

6.2 调试技巧

添加调试输出

void debug_contests(const vector<Contest>& contests) {
    cout << "排序后的比赛:" << endl;
    for (const auto& c : contests) {
        cout << "开始:" << c.start << " 结束:" << c.end << endl;
    }
}

// 在选择过程中输出信息
for (int i = 0; i < n; i++) {
    cout << "检查比赛: " << contests[i].start << "-" << contests[i].end;
    if (contests[i].start >= lastEnd) {
        cout << " ✓ 选择" << endl;
        count++;
        lastEnd = contests[i].end;
    } else {
        cout << " ✗ 冲突" << endl;
    }
}

七、竞赛应用总结

7.1 解题思路模板

  1. 识别问题类型:活动安排/线段覆盖问题
  2. 设计数据结构:存储开始和结束时间
  3. 确定排序规则:按结束时间升序排序
  4. 实现贪心选择:选择不冲突的结束时间最早的活动
  5. 处理边界情况:初始值和比较条件的正确处理

7.2 考场实战技巧

  • 记住经典解法:活动安排问题必用结束时间排序
  • 注意数据范围:n最大10⁶,必须使用高效算法
  • 验证样例:用题目样例验证算法正确性
  • 检查边界:开始时间可能为0的情况

八、扩展学习

8.1 类似题目推荐

  1. P2240部分背包:贪心选择性价比
  2. P1223排队接水:短作业优先贪心
  3. P1090合并果子:优先队列贪心
  4. P1478陶陶摘苹果:简单贪心选择

8.2 算法思维拓展

贪心算法的应用场景

  • 区间调度:结束时间最早优先
  • 哈夫曼编码:频率高的字符用短码
  • 最小生成树:Prim和Kruskal算法
  • 单源最短路径:Dijkstra算法

📚 学习资源推荐

  • 推荐练习:洛谷P1803、P2240、P1223
  • 理论深化:《算法导论》贪心算法章节

💎 实战建议

  • 掌握经典模型:活动安排问题是贪心算法的经典应用
  • 理解证明思路:明白为什么结束时间最早最优
  • 熟练编码实现:快速实现排序和贪心选择逻辑

✨ 本文提供的贪心算法解法已通过严格测试,能够正确处理n≤10⁶的所有情况!


  🔥 关注我,解锁CSP-J/S竞赛全攻略 🔥

(每日更新高频考点 + 精选真题解析,助你轻松备赛!)
👇 点击关注立即提升竞赛战力 👇
[https://blog.csdn.net/stillwatersss]


📚 专栏亮点抢先看

  1. 高频考点突破

    • 每日题解:精选洛谷/LeetCode CSP-J/S经典真题,附详细题解与时间复杂度优化技巧
    • 考点拆解:动态规划、图论、字符串算法等核心专题深度剖析,直击竞赛命题规律
    • 实战模板:限时领取《C++竞赛模板大全》👉 关注后私信回复“模板”获取
  2. 备赛效率翻倍技巧

    • 从O(n²)到O(n):独家算法优化套路,解决TLE超时问题
    • 考场避坑指南:常见失分点分析 + 数据边界处理技巧
    • 互动答疑:评论区留言题目编号,优先解析你的个性化难题
  3. 独家福利🌟

    • 粉丝专享:高价值文章设为 “仅粉丝可见”(如《CSP-J/S近5年考点分布与预测》)
    • 资料包:关注后私信 “资料” 领取 竞赛真题库+调试代码工具包

💡 为什么值得关注?

数据驱动:内容基于CSP-J/S真题大数据,命中率超80%
即学即用:每篇附可运行代码(代码通过洛谷测评)与测试用例
垂直领域:专注竞赛辅导,拒绝泛技术水文,直击备赛痛点

📢 今日关注福利:前100名新粉丝回复【进阶】赠送《洛谷青铜~黄金段位进阶题库》📘
🔥 行动提示:点击主页 → 专栏 → 开启订阅更新,系统自动推送最新解析!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杨小码不BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值