贪心专题探讨
导言
有人说,在古代以及近代,维护信任的纽带大多是道德与名誉。而到了现代,只有利益才能够稳定地维护信任。但其实,道德与名誉在古时显然也是一种利益。道德带来信誉,良好的信誉是邻里关系的基础,这是社交环境中最大的利益。人们拥有不同理智程度的价值观,以自己的价值观为准绳,去进行利益最大化,也就是所谓的贪心。而为了最大化特定群体的利益,统治者需要规范人们的价值观,于是意识形态便出现了。
许多人利益最大化的首要受众一般是自我个人或代表自我的一个小团体或神,因此产生了奴隶、封建、神权等意识形态。而后受众逐渐变成“人们”、“人民”,例如资本主义 (CAPITALISM) 认为每个人贪心的受众是自己的财产、共产主义 (COMMUNISM) 认为每个人贪心的受众是群体。意识形态是强加给受众的价值观,现代以绝大部分人为利益受众的价值观都是历史上伟大的思想家为了改进人们普遍不理智的价值观而做出的实践。这些意识形态是为了让人们拥有更理智的贪心观,从而使社会能创造更多利益。
因此,无论古今,无论中外,各种不同的意识形态告诉我们,人们都是贪心的。
南辕北辙的故事告诉我们,贪心的正确度不同,就决定了这条道路是明亮还是晦暗。在如今的美国,无论怎么走,走的都是下坡路。而在正确的思想指引下,无论怎么走都有上坡路。所以在这一章节中,让我们通过一些例子来研习并口头探讨什么才是正确的贪心。
通法:
1. 考虑朴素的 dp 方法来获得启发
2. 按某种顺序考虑所有选项
3. 以某种比较方法尝试替换之前的某个选项
某种顺序:
通常指按限制的严格程度由严到宽
某种比较方法
通常指交换后是否能使答案不劣
某个选项
通常指占用/花费最大的选项
另法:
如果能确定 "选择了 \(A\) 那么一定会选 \(B\) " ,就可以把 \(A\) 和 \(B\) 放进一个联通块里面。
P2949 [USACO09OPEN] Work Scheduling G
从 \(0\) 时刻开始,有 \(10^9\) 个单位时间。在任一时刻,他都可以选择编号 \(1\) 到 \(N\) 的 \(N(1 \leq N \leq 10^5)\) 项工作中的任意一项工作来完成。 每个单位时间里只能做一个工作。 对于第 \(i\) 个工作,有一个截止时间 \(D_i(1 \leq D_i \leq 10^9)\),如果他可以完成这个工作,那么他可以获利 \(P_i( 1\leq P_i\leq 10^9 )\). 在给定的工作利润和截止时间下,约翰能够获得的利润最大为多少.
某种顺序:截止时间由小到大
某种比较方法:利润大小
某个选项:利润最小的选项
P1650 田忌赛马
齐王和田忌各有 \(n\) 匹马,每个马有速度值 \(v_i\) ,\(n\le 2000\)
某种顺序:田忌马的速度
某种比较方法:是否能赢
某个选项:任意一头没被匹配的齐王的马
把两人马的速度分别从大到小排序。最优答案显然是速度最快的几匹马赢,所以每匹田忌的马都去匹配还未匹配到的速度小于该马的速度最快的马,如果匹配不到,就结束循环,输出答案。
[JSOI2007] 建筑抢修
\(n\) 个建筑需要抢修,每个建筑抢修耗时为 \(T1_i\) , 如果没有在 \(T2_i\) 之前抢修,建筑就报废。问最多能抢修多少个建筑。
\(1 \le n < 150000\),\(1 \le T1 < T2 < 2^{31}\)。
考虑 dp : \(f_t\) 表示按照 \(T2\) 排序的前 \(i\) 个建筑中,\(t\) 时间之内最多抢修的建筑数量。
式子:
sort([t1,t2]) => (t2[i] <= t2[i+1]);
fi(1,n){
for(int t=t2[i];t>=t1[i];--t)
f[t]=max(f[t],f[t-t1[i]]+1);
ff(t,1,t2[i])
f[t]=max(f[t],f[t-1]);
}
ans=f[t2[n]];
我们发现如果 \(T_{max}+T1 \le T2\) ,那么 \(i\) 会让最大值 \(+1\) ,否则可能会让最大值出现的时间 \(t\) 提前。因此得出贪心:
某种顺序:截止时限由小到大
某种比较方法:报废是否是必要的
某个选项:耗时最大的建筑
如果在考虑某个建筑的时候,发现必须要报废一个了,那就把耗时最大的建筑(包括自己也考虑在内)给放弃掉。
P2107 小Z的AK计划
数轴上 \(n\) 个给定的点分别代表一道题,每个点坐标为 \(x_i >0\),需要花费 \(t_i\) 的时间才能解决这道题。小 Z 移动一格消耗时间 \(1\) ,初始在 \(0\)。问 \(m\) 时间以内能解决多少题。
某种顺序:\(x\) 由大到小
某种比较方法:能否让解决题目数量不减的情况下让时间最短。
某个选项:\(x\) 最大的选项和 \(t\) 最大的选项
代码:https://www.luogu.com.cn/paste/inuywjby
这道题的选项好像没有什么限制,所以还可以这样贪:
某种顺序:\(x\) 由小到大
某种比较方法:能否让解决题目数量不减的情况下让时间最短。
某个选项:\(t\) 最大的选项
这样做的话答案就要在枚举每道"必须做的题目"时更新。
CF865D Buy Low Sell High
\(n\) 天,第 \(i\) 天股价为 \(p_i\) ,每天只能买卖共一股。求最大盈利。持股数量不能为负。 \(n \le 3\times 10^5 ,1 \le p_i \le 10^6\)
没有明显思路,考虑 dp:
\(f_{i,j}\) 代表第 \(i\) 天持有 \(j\) 股的最大收益。
\(f_{i,j}=max(f_{i-1,j-1}-p_i,f_{i-1,j},f_{i-1,j+1}+p_i)\)
选项也没有明显限制,因此我们按照天数贪。
可以发现卖出最低价应该比买入最高价高。也就说有一个界限值,这个值以上的尽量卖,以下的尽量买。
每个新的一天都先卖这个股票,并在之前没有卖的股票里面选最大的一个买。
某种顺序:\(i\) 由小到大
某种比较方法:所考卖出项是否在界限值以上,是否比 \(p\) 最小的卖出项优。【所考卖出项:所考虑的卖出项】
某个选项:\(p\) 最小的卖出选项
代码链接:https://www.luogu.com.cn/paste/yqx63eqs
顺带一提,这道题的题解代码和 CF13C Sequence 的题解代码如出一辙。有理由怀疑 CF13C 也可以贪心。
本题(代码中将 sell 和 unused 合并成了 q ,也就是所有可以被买进的项):
ll ans;
int a,n;
priority_queue<int,vector<int>,greater<int>>q;
int main(){
scanf("%d",&n);
fi(1,n){
scanf("%d",&a);
q.push(a);
if(a>q.top()){
ans+=a-q.top();
q.pop();
q.push(a);
}
}
printf("%lld\n",ans);
return 0;
}
CF13C:
ll ans;
int a,n;
priority_queue<int>q;
int main(){
scanf("%d",&n);
fi(1,n){
scanf("%d",&a);
q.push(a);
if(a<q.top()){
ans+=q.top()-a;
q.pop();
q.push(a);
}
}
printf("%lld\n",ans);
return 0;
}
P3545 [POI2012] HUR-Warehouse Store
现在有 \(n\) 天。第 \(i\) 天上午会进货 \(A_i\) 件商品,中午的时候会有顾客需要购买 \(B_i\) 件商品,可以选择满足顾客的要求,或是无视掉他。
如果要满足顾客的需求,就必须要有足够的库存。问最多能够满足多少个顾客的需求。
\(1\leqslant n\leqslant 2.5\times 10^5\),\(0\leqslant a_i,b_i \leqslant 10^9\)。
某种顺序:时间顺序
某种比较方法:是否能让剩余商品变多(也就是所考项的 \(B\) 小于某项的 \(B\))
某个选项:\(B\) 最大的已选项
代码链接:https://www.luogu.com.cn/record/188550301
P1484 种树
给定 \(n,K,a\)。 \(n \le 3\times 10^5\) 个坑位可以种树。树不能相邻。你最多种 \(K \le \frac{n}{2}\) 棵。在 \(i\) 位置种树的收益是 \(-10^6 \le a_i \le 10^6\)
记录状态 \(f(i,p,k,S)\) 代表 \(i\) 坑种了树的最大收益 \(p\) 以及种了 \(k\) 棵树分别为 \(S_1,S_2,\dots,S_k\)。对于每个不同的 \(i\) 只保留:
-
\(p\) 最大
-
\(k\) 最小
的一个。以下省略 \(S\)
那么如果 \(f(i,p,k < K)\) ,就继承 \(f(j \not=i-1,\max p',k-1)\) 的状态。
否则如果 \(f(i,p,K) ,\)就一定会在 \(f(j \not=i-1,p,K)\) 里面选择一个,去掉一个贡献最小的树过后加上 \(i\) 位置的树来继承。
我猜测在 \(k=K\) 时, \(P\) 越大,价值最小的树的价值不减。这样的话就可以 \(f(i,p,K) \leftarrow f(j,\max p',K) - \min(a | a\in S')+a_i\)
写个代码验证一下猜想。
啊哦,是错的。 7 3 \n 1 2 1 0 1 0 1
能够 hack 掉。
正确做法:按 \(a_i : + \infty \rightarrow - \infty\) 枚,对于一个 \(a_i\) ,要么同时选择 \(a_i\) 与任意一个不相邻的 \(a_j\) ,否则就证明 \(\exists \;a_{[1,i-1]} + a_{[i+1,n]} \geqslant a_i + \forall a_j\)。如果 \(a_{[1,i-1]} : a_{\text{sth.} \not=i-1}\),那么显然 \(a_j : a_{[1,i-1]}\) 最右。所以 \(a_{[1,i-1]} : a_{i-1}\) 。因此此时要同时选择 \(a_{i-1}\) 和 \(a_{i+1}\) 。所以我们可以把 \(a_{i-1} 和 a_{i+1}\) 所在的联通块合并。 合并的时候可以顺便考虑对答案的贡献。
代码链接:https://www.luogu.com.cn/record/189904358
CF730I Olympiad in Programming and Sports
\(n \le 10^5\) 个人,每个人两个属性 \(1 \le a_i ,b_i\) 。给定 \(A,B\),选出 \(A\) 个人获得其 \(a\) ,选出 \(B\) 个人获得其 \(b\) 。求最大总获得。
旁边巨佬的思路:
任选 \(A+B\) 个属性的时候,一定是让 \(b-a\) 较小的获得 \(a\) ,\(b-a\) 较大的获得 \(b\)。推广到 \(n\) 个属性的时候,一定是在 \(b-a < K\) ( \(K\) 是一个阈值)的部分中选择 \(a\) ,在 \(b-a \geqslant K\) 的部分选择 \(b\) 。证明:如果有两个值 \(b_i+a_j > a_i +b_j\),也就是 \(b_i-a_i > b_j - a_j\),那么 \(i\) 一定选择 \(b\) , \(j\) 一定选择 \(a\) 。
代码链接:https://codeforces.com/contest/730/submission/292473190
[AGC018C] Coins
\(n \le 10^5\) 个人,每个人三个属性 \(1 \le a_i,b_i,c_i\) 。给定 \(A,B,C\),保证 \(A+B+C=n\),选出 \(A\) 个人获得其 \(a\) ,选出 \(B\) 个人获得其 \(b\),选出 \(C\) 个人获得其 \(c\) 。求最大总获得。
既然保证了 \(A+B+C=n\),那么就先让 \(n\) 个全选 \(c\)。
问题就转化成了选 \(A\) 个 \(a-c\) 和 \(B\) 个 \(b-c\) 个 \(n\) 个 \(c\)。