8. 贪心
题目 两极分化
-
贪心算法
-
即贪心策略 : 用局部最优找出全局最优
-
把解决问题的过程分成若干步
-
每一步都用看起来最优的解法
-
以此希望 局部最优 -> 全局最优
-
-
-
贪心算法的特点
-
贪心 -> 贪婪 + 鼠目寸光 -> 有可能出现错误 -> 需要证明策略正确 (反证/数学归纳/交换论证/...)
-
问题不同 , 贪心策略不同 -> 没有固定模板
-
-
学贪心的方法
-
认知 : 遇到新的贪心题 若不会 很正常
-
前期学习 , 重在学习策略 , 把策略当经验吸收
-
练习时 , 尽可能证明策略的正确性
-
8.1 简单贪心
8.1.1 货仓地址
!洛谷
P10452 货仓选址
题目描述
在一条数轴上有 N N N 家商店,它们的坐标分别为 A 1 ∼ A N A_1 \sim A_N A1∼AN。
现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。
为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。
输入格式
第一行输入整数 N N N。
第二行 N N N 个整数 A 1 ∼ A N A_1 \sim A_N A1∼AN。
输出格式
输出一个整数,表示距离之和的最小值。
输入输出样例 #1
输入 #1
4 6 2 9 1输出 #1
12说明/提示
数据保证, 1 ≤ N ≤ 10 5 1 \le N \le 10^5 1≤N≤105, 0 ≤ A i ≤ 40000 0 \le A_i \le 40000 0≤Ai≤40000。
思路:
结论 : 建立在中位数的地方 (n为奇 -> 中位数 , n 为偶 -> 两个中位数都可以), 距离之和最小
注意要排序
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
LL n;
LL a[N], sum;
int main()
{
cin >> n;
for(int i = 1; i <= n; i++ ) cin >> a[i];
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; i++) sum += abs(a[n/2] - a[i]);
cout << sum;
return 0;
}
证明:
绝对值不等式 : ∣ a − x ∣ + ∣ b − x ∣ ≥ ∣ a − b ∣ |a-x|+|b-x| ≥ |a-b| ∣a−x∣+∣b−x∣≥∣a−b∣
性质 : 当 x 在 ( a , b ) (a,b) (a,b) 之间时, 取等号 , 并取最小值
结论 :
形如
sum = ∑ i = 1 n ∣ a [ i ] − x ∣ = ∣ a [ 1 ] − x ∣ + ∣ a [ 2 ] − x ∣ + ... + ∣ a [ n ] − x ∣ \text{sum} = \sum_{i=1}^{n} |a[i] - x| = |a[1] - x| + |a[2] - x| + \ldots + |a[n] - x| sum=i=1∑n∣a[i]−x∣=∣a[1]−x∣+∣a[2]−x∣+...+∣a[n]−x∣
的式子
- 当 x 取得 n 个数的中位数时 , 和最小
- 和最小为 : ( a [ n ] − a [ 1 ] ) + ( a [ n − 1 ] − a [ 2 ] ) + ... + ( a [ n + 1 − n / 2 ] − a [ n / 2 ] ) (a[n]−a[1])+(a[n−1]−a[2])+...+(a[n+1−n/2]−a[n/2]) (a[n]−a[1])+(a[n−1]−a[2])+...+(a[n+1−n/2]−a[n/2])
先大胆猜想策略 , 然后验证
8.1.2 最大子段和
!洛谷
P1115 最大子段和
题目描述
给出一个长度为 n n n 的序列 a a a,选出其中连续且非空的一段使得这段和最大。
输入格式
第一行是一个整数,表示序列的长度 n n n。
第二行有 n n n 个整数,第 i i i 个整数表示序列的第 i i i 个数字 a i a_i ai。
输出格式
输出一行一个整数表示答案。
输入输出样例 #1
输入 #1
7 2 -4 3 -1 2 -4 3输出 #1
4说明/提示
样例 1 解释
选取 [ 3 , 5 ] [3, 5] [3,5] 子段 { 3 , − 1 , 2 } \{3, -1, 2\} {3,−1,2},其和为 4 4 4。
数据规模与约定
- 对于 40 % 40\% 40% 的数据,保证 n ≤ 2 × 10 3 n \leq 2 \times 10^3 n≤2×103。
- 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 2 × 10 5 1 \leq n \leq 2 \times 10^5 1≤n≤2×105, − 10 4 ≤ a i ≤ 10 4 -10^4 \leq a_i \leq 10^4 −104≤ai≤104。
2026/01/21:增加一组 hack 数据。
思路:
贪心策略 :
-
从第一个元素起 累加
-
sum >= 0 : 继续累加
-
sum < 0 : 舍去这一段 , 从下一个位置开始重新累加
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int main()
{
int n;
cin >> n;
LL sum = 0 , a , ret = -1e6;
while(n--)
{
cin >> a;
sum += a;
ret = max(ret , sum);
if(sum < 0) sum = 0;
}
cout << ret << endl;
return 0;
}
证明:
------±-----±----±-----±----±---
----- a---- c --- k1 -- b -- k2
-
从 ( a , b ) (a,b) (a,b) 之间任何一个点开始 , 都不可能是最优解
-
反证法 :
-
从c点开始 , 向后累加时 , 在 ( a , b ) (a,b) (a,b) 之间拿到更优解
s u m [ c , k ] > s u m [ a , k ] sum[c,k] > sum[a,k] sum[c,k]>sum[a,k] -> s u m [ a , c ] < 0 sum[a,c]<0 sum[a,c]<0 -> 与贪心策略矛盾 -> 所以贪心策略正确
-
从c点开始 , 向后累加时 , 越过b点 , 拿到最优解
s u m [ c , b ] > s u m [ a , b ] sum[c,b] > sum[a,b] sum[c,b]>sum[a,b] -> s u m [ a , c ] < 0 sum[a,c] < 0 sum[a,c]<0 -> 与贪心策略矛盾 -> 所以贪心策略正确
-
看着思路离谱 , 在贪心算法中 , 很正常 , 重要的是证明