1. 反悔贪心算法基础解析
1.1 贪心算法的局限与突破
贪心算法在信奥赛解题中一直是个让人又爱又恨的存在。作为C++选手,我们都经历过那种"局部最优不等于全局最优"的绝望时刻。记得去年省赛有一道资源分配题,我用标准贪心只能拿到70分,赛后才知道正解需要反悔机制。这种算法之所以被称为"带后悔药的贪心",正是因为它允许我们在后续步骤中推翻先前的局部最优选择。
从时间复杂度来看,基础贪心通常是O(n),而反悔贪心由于需要维护优先队列,往往会增加到O(n log n)。但这个代价换来的可能是分数从70分到AC的飞跃。在CSP-J/S这类竞赛中,反悔贪心常出现在任务调度、资源分配、区间选择等题型,特别是当题目出现"允许放弃之前选择"这类字眼时,就要立即想到这个算法。
1.2 反悔机制的核心原理
反悔贪心的精妙之处在于它用数据结构记录了所有可能的选择机会。以优先队列实现为例,当我们选择当前最优元素后,会将其反悔代价(通常是取反后的值)重新放入队列。这个操作相当于给自己留了条后路,当后续出现更优解时,可以通过"反悔"操作收回之前的决定。
这里有个关键的计算公式:
反悔代价 = 新元素值 - 被替换元素值
例如在任务调度问题中,如果我们已选择任务A(价值5),遇到任务B(价值8),则反悔代价为8-5=3。这个值会被存入队列,后续可能与其他任务产生新的组合。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 典型问题建模与实现
2.1 任务调度问题实战
让我们看一个CSP-J经典题型:有n个任务,每个任务有截止时间d和收益p,如何选择任务使总收益最大?这个问题用标准贪心(按截止时间排序)会漏掉很多高收益机会。
cpp复制struct Task {
int d, p;
bool operator<(const Task& t) const {
return p < t.p; // 大顶堆
}
};
int maxProfit(vector<Task>& tasks) {
sort(tasks.begin(), tasks.end(), [](const Task& a, const Task& b) {
return a.d < b.d; // 按截止时间排序
});
priority_queue<Task> pq;
int current_time = 0, total = 0;
for (auto& task : tasks) {
if (current_time < task.d) {
pq.push(task);
current_time++;
total += task.p;
} else if (!pq.empty() && pq.top().p < task.p) {
total += task.p - pq.top().p;
pq.pop();
pq.push(task);
