贪心题一般没有什么技巧,多做题积累经验。
对于结论或策略,大胆猜想,小心求证,注意使用数据结构优化/结合其他算法。
一般类贪心
主要是证明贪心的正确性。
H. Fight Against Monsters
先用二分求出每个怪需要打的次数。
问题转化为
一个排列的答案是
考虑邻项交换 \(i,i+1\),答案的变化就为
如果交换更优,应满足
即
按照属性升序排序即可。
打怪兽2
对于 \(b_i \ge a_i\) 的肯定先打,按照 \(a_i\) 排序。
对于 \(b_i < a_i\) 的,考虑邻项交换。
设现在还剩血量 \(t\),面对怪兽 \((a_i,b_i),(a_{i+1},b_{i+1})\),若不交换更优,应满足
由 \(b_i<a_i\)
只能有
进一步
即
按照 \(b\) 降序排序即可。
反悔贪心
P2949 [USACO09OPEN] Work Scheduling G
题目大意:给个任务有一个截止时间,每个时间安排一个任务,求最大利润。
朴素贪心想法:按照截止时间排序,优先做利润大的。
问题在于如果后面出现了利润更大的,但前面的已经被安排满了,就会导致贪心策略错误,故这种情况就要反悔操作。
使用一个优先队列维护一个做过的决策集合,如果当前任务还能做,直接加入集合。否则,从之前的决策中取利润最小的替换掉。
这个操作等价于没有做之前的利润小的任务,直接做了利润更大的任务,从局部最优满足了全局最优。
sort(a+1,a+1+n);
for(int i=1;i<=n;++i)
{if(a[i].d<=Q.size()){if(Q.top()>a[i].p) continue;else{int x=Q.top();Q.pop();ans+=a[i].p-x;Q.push(a[i].p);}}else{ans+=a[i].p;Q.push(a[i].p);}
}
P4053 [JSOI2007] 建筑抢修
题目大意:每个任务有所需时间和截止时间,求最多能安排多少任务。
朴素贪心:按照截止时间排序,扫一遍判断是否能做。
此时就会有无法做的任务,我们会直接舍弃,给前面一个任务很多的时间,贪心错误,考虑反悔。
用一个大根堆维护决策集合,具体维护其时间。如果当前时间能够做这个任务,就直接做。否则,一定有一个任务是不能做的,只能尽可能地压缩当前时间,以保证后面能安排更多的任务。于是考虑从大根堆中取出耗费时间最多的决策去除,消去其贡献。
注意一定要先贪心再反悔。
贪心证明:
假设我们确定了要选 \(S\) 中的所有任务,通过邻项交换推导策略。
记之前已经用的时间为 \(t\),即 \(\sum_{k=1}^{i-1} a_k = t\)
对于相邻的 \(i,j\),交换前更优,即满足交换后就没法完成第二个任务了,有
交换后有
整理一下,有
故是按照截止时间排序。
int now=0,cnt=0;
for(int i=1;i<=n;++i)
{now+=a[i].t1;Q.push(a[i].t1);if(now<=a[i].t2){++cnt;}else{int x=Q.top();Q.pop();now-=x;}
}
然后就可以通过转化,将这个问题建模为其他问题:
Zabuton
将每个人抽象成任务,拥有任务所需时间和截止时间,那么添加的砖就是任务所需时间,最初的砖不能超过 \(H\),等价于最后的砖不能超过 \(H+P\),可以转化为截止时间。
for(int i=1,p,h;i<=n;++i)
{h=read(),p=read();a[i].t1=p,a[i].t2=p+h;
}