贪心 学习笔记
概述
贪心算法指每一步都选当前的最优解,不考虑这一步对后面的影响。使用时需要先证明贪心策略的正确性。一般使用反证法或数学归纳法证明。
例题
这一题要求的是一个最优的排列,考虑反证。假设当前的排列最优,尝试交换相邻两个大臣生成一个新的排列。
设前面所有人左手的乘积为 \(S\)。交换前两个人的最大值是 \(\max(\frac{S}{b_i},\frac{Sa_i}{b_{i+1}})\),交换后为 \(\max(\frac{S}{b_i+1},\frac{Sa_{i+1}}{b_{i}})\)。如果交换后更劣,有:
\[\begin{aligned}\max(\frac{S}{b_i},\frac{Sa_i}{b_{i+1}})&<\max(\frac{S}{b_{i+1}},\frac{Sa_{i+1}}{b_{i}})\\\max(\frac{1}{b_i},\frac{a_i}{b_{i+1}})&<\max(\frac{1}{b_{i+1}},\frac{a_i+1}{b_{i}})\\\max(b_{i+1},a_ib_i)&<\max(b_i,a_{i+1}b_{i+1})\\a_ib_i\leq a_{i+1}b_{i+1}\end{aligned}
\]
按照这个式子排序再统计答案即可。
反悔贪心
堆
容易想到一个简单的贪心策略:优先找截止时期靠前的做。
但是可以 hack:3 1 1 2 114514 2 1919810
。可能在某一时刻有很多高利润的任务截止,只能挑一个做。
这时可以反悔,将之前做的利润最低的任务退掉做这一个任务。用一个堆维护做过的任务即可。
#include<bits/stdc++.h>
using namespace std;
int n;
long long ans;
pair<int,int>a[100005];
priority_queue<int,vector<int>,greater<int> >q;
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i].first>>a[i].second;
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
if(q.size()<a[i].first)ans+=a[i].second,q.push(a[i].second);
else (q.top()<a[i].second)&&(ans+=(a[i].second-q.top()),q.pop(),q.push(a[i].second),0);
}
return cout<<ans<<'\n',0;
}
双向链表
还是先举一个简单的贪心策略,优先挑获利最大的种。
hack:4 3 4 3 1
。如果按照上面的策略,选出的是 \(4+1=5\),但是答案是 \(3+3=6\),因为选 \(4\) 比选相邻的 \(3+3\) 劣。
可以设计一个反悔机制,在选取了某个点后,可以把这个点退掉,改为选旁边两个点。在选了一个点后,把旁边两个点删除,而把这个点的获利改为旁边两个点的获利减这个点的获利。这个过程可以用双向链表维护。
如果再选一遍这个点,变成了这样:
此时 \(2\sim6\) 都不能选,收益是 \(v_4+(v_3+v_5-v_4)=v_3+v_5\)。这和选 \(3,5\) 的情况是一样的,相当于把 \(4\) 退掉而选了 \(3,5\)。
#include<bits/stdc++.h>
using namespace std;
int n,k;
bool vis[500005];
long long ans;
struct node{
long long v;
int l,r;
}a[500005];
priority_queue<pair<long long,int>>q;
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i].v,a[i].l=i-1,a[i].r=i+1,q.push(make_pair(a[i].v,i));
for(int i=1;i<=k;i++){
while(vis[q.top().second])q.pop();
int now=q.top().second;
if(q.top().first<=0)break;
ans+=q.top().first,q.pop(),vis[a[now].l]=vis[a[now].r]=1;
a[now].v=a[a[now].l].v+a[a[now].r].v-a[now].v;
q.push(make_pair(a[now].v,now));
a[now].l=a[a[now].l].l,a[now].r=a[a[now].r].r;
a[a[now].l].r=a[a[now].r].l=now;
}
cout<<ans<<'\n';
return 0;
}
[[杂项]]