六 贪心 贪心问题没有普遍统一的模板 ,跳跃性很强,结论证明往往很难。
做法:1.找以往做过的相似的题目 2.不会的只能靠猜了
贪心是求解一类最优化问题的方法,考虑当前状态下局部最优 的策略,来使全局 结果达到最优 。
贪心的证明往往比贪心更难。
代码随想录:贪心专题文章 ,题目数量多,总结全面。LeetCode101贪心算法专题。
要证明贪心策略的正确性常会用到反证法,找一找是否有反例,还有一种就是数学归纳法。
做题时没必要非得证明出来,能AC就能成功。
1 acwing.1055. 股票买卖 II(LeetCode) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 输入格式 第一行包含整数 N,表示数组长度。 第二行包含 N 个不大于 10000 的正整数,表示完整的数组。 输出格式 输出一个整数,表示最大利润。 数据范围 1 ≤N≤10 ^5 输入样例1 : 6 7 1 5 3 6 4 输出样例1 : 7 输入样例2 : 5 1 2 3 4 5 输出样例2 : 4 输入样例3 : 5 7 6 4 3 1 输出样例3 : 0 样例解释 样例1 :在第 2 天(股票价格 = 1 )的时候买入,在第 3 天(股票价格 = 5 )的时候卖出, 这笔交易所能获得利润 = 5 -1 = 4 。随后,在第 4 天(股票价格 = 3 )的时候买入,在第 5 天(股票价格 = 6 )的时候卖出, 这笔交易所能获得利润 = 6 -3 = 3 。共得利润 4 +3 = 7 。 样例2 :在第 1 天(股票价格 = 1 )的时候买入,在第 5 天 (股票价格 = 5 )的时候卖出, 这笔交易所能获得利润 = 5 -1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 样例3 :在这种情况下, 不进行任何交易, 所以最大利润为 0 。
思路:
根据数据范围,时间复杂度应该控制为:O(n)或者O(n*logn)。
交易策略:对于数组的相邻两天,只要后一天比前一天价格高,就进行一次交易。
性质:任意交易天数跨度大于1的交易,必然可以分解为多个交易天数等于1的交易。
代码:
时间复杂度:O(n).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <cstdio> #include <cstring> #include <algorithm> #include <iostream> using namespace std ;const int N = 1e5 +10 ;int n;int price[N];int main () { scanf ("%d" ,&n); for (int i = 0 ;i < n;i ++) scanf ("%d" ,&price[i]); int res = 0 ; for (int i = 0 ;i < n-1 ;i++){ int dt = price[i+1 ] - price[i]; if (dt > 0 ) res += dt; } printf ("%d\n" ,res); return 0 ; }
2 acwing.104. 货仓选址 《算法竞赛进阶指南》, 模板题
本题在寒假每日一题 中出现过。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 在一条数轴上有 N 家商店,它们的坐标分别为 A1∼AN。 现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。 为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。 输入格式 第一行输入整数 N。 第二行 N 个整数 A1∼AN。 输出格式 输出一个整数,表示距离之和的最小值。 数据范围 1 ≤N≤100000 ,0 ≤Ai≤40000 输入样例: 4 6 2 9 1 输出样例: 12
思路:
从直觉上看,我们不妨猜货仓应该建在所有商店的中间位置。
这很容易证明,如果建在两个商店之外,距离一定比在中间大。
从2个、3个商店,手动模拟一下,很容易发现上述规律,应该建在中位数这个位置。
出题人往往根据已有的数学模型,包装成一个题目,用贪心求解。
严格数学证明:
问题核心在于分组求绝对值之和最大值。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <cstdio> #include <iostream> #include <algorithm> using namespace std ;typedef long long LL;const int N = 100010 ;int n;int a[N];int main () { scanf ("%d" ,&n); for (int i = 0 ;i < n;i++) scanf ("%d" ,&a[i]); sort(a,a+n); int c = a[n/2 ]; LL res = 0 ; for (int i = 0 ;i < n;i++) res += abs (a[i] - c); printf ("%lld" ,res); return 0 ; }
3 acwing.122. 糖果传递(困难) 《算法竞赛进阶指南》,微软面试题 , HAOI2008,有难度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 有 n 个小朋友坐成一圈,每人有 a[i] 个糖果。 每人只能给左右两人传递糖果。 每人每次传递一个糖果代价为 1 。 求使所有人获得均等糖果的最小代价。 输入格式 第一行输入一个正整数 n,表示小朋友的个数。 接下来 n 行,每行一个整数 a[i],表示第 i 个小朋友初始得到的糖果的颗数。 输出格式 输出一个整数,表示最小代价。 数据范围 1 ≤n≤1000000 ,0 ≤a[i]≤2 ×10 ^9 ,数据保证一定有解。 输入样例: 4 1 2 5 4 输出样例: 4
思路:数学演算
将所有量进行数学化计算,构建数学模型,最终结果就是极小化红色标注的绝对值不等式,然后就可以直接套用上一题货仓选
址的做法。($x_1$等表示从$a_1$传递到$a_2$的糖果数量,可正可负,如果是负数表示是反向传递的)
下一步,要证明 方案是否一定存在。(如果只想AC可以不看证明)
比如,当a1=3时,x3=5,方案就不成立,因为给出的糖果不可能比已有的还多。
首先可以证明:
所有$x_i(i=1,2,…,n)$同时大于0不成立。假设所有$x_i$大于0,那么可以让所有$x_i$都减去最小的$x_i$,使得每个
人得到的糖果数量是一样的效果,因为相互抵消了,这是就有部分$x_i$等于0,部分大于0,所以不成立。
同理可以证明所有$x_i(i=1,2,…,n)$同时小于0也不成立。
由上述结论可知:存在部分$x_i>=0$,部分<0。
一定存在至少一个分界点$a_m$满足左边>=0,右边<0。也就是说这个人一定往两边传递糖果。
那我们可以从分界点处把环变成一条直线。
对于每一个$x_k$,分成两种情况,当$x_k>=0$时,糖果立即传递给右边;当$x_k<0$时,先不给。
我们先考虑糖果从左往右传递的情况,可以得到上上图的两个式子。
第一个式子:当$x_{k-1}>=0$时,$a_1$到$a_{k-1}$的糖果数小于(我觉得是等于)平均值。
然后再根据第二个式子,就能得到$a_k >= x_k$,得证。
从右往左传递糖果时,dfs回溯。
以上证明实在看不懂可以略过。(不影响AC)
参考题解:https://www.acwing.com/solution/content/5531/
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <cstdio> #include <cstring> #include <algorithm> #include <iostream> using namespace std ;typedef long long LL;const int N = 1000010 ;LL c[N]; LL s[N]; int n;int main () { scanf ("%d" ,&n); for (int i = 1 ;i <= n;i++){ scanf ("%lld" ,&s[i]); s[i] += s[i-1 ]; } LL ave = s[n]/n; int k = 0 ; for (int i = 1 ;i < n;i++){ c[k++] = i*ave - s[i]; } c[k++] = 0 ; sort(c,c+k); LL m = c[k/2 ]; LL res = 0 ; for (int i = 0 ;i < n;i++) res += abs (c[i] - m); printf ("%lld" ,res); return 0 ; }
补充:
快速选择函数nth_element(数组初位置,寻找元素,数组末位置)(STL)
也可以自己实现,利用快排 。
C++的STL库中的nth_element()方法,默认是求区间第k小 的(划重点)。
举个栗子求第3小,对于 a[9]={4,7,6,9,1,8,2,3,5};
nth_element(a,a+2,a+9),将下标为2,也就是第3个数放在正确的位置,求的是第3小的数a[2] 。(下标从零开始)
寒假每日一题入门题(一)中介绍过两种题解。
1.sort,时间复杂度:O(n*logn) 2.nth_element,时间复杂度:O(n)